You can really get a feel for what authoring a simple web app is like in Kitten (with the latest techniques/features), by checking out the source code for Look Over There!, the multi-site forwarding app with TLS forwarding support that I built recently:
• The admin page (notice the lock emoji at the end of the name? That’s all you need to add to a route to make use of Kitten’s automatic authentication. Since every Kitten site/app is protected by public-key encryption and we don’t have the concept of users (each site/app on the Small Web is owned by one person), the whole process can be automated for you. https://codeberg.org/small-web/look-over-there/src/branch/main/admin%F0%9F%94%92.page.js
• The Redirection component. This is what does all the hard work on the admin page. Along with the admin page, they showcase Kitten’s new/alternative (and as-of-yet mostly undocumented) class-based component model with event bubbling on the component hierarchy on the server. (Since this is resource intensive, it’s recommended you use it only when implementing authenticated routes where you’re sure only the site’s owner will be accessing the route.) https://codeberg.org/small-web/look-over-there/src/branch/main/Redirection.component.js
In any case, as Kitten and the rest of the Small Web ecosystem matures further, I’ll be documenting all this better and there will be even more examples and tutorials but, in case you’re one of those inquisitive types and you want to see what web development can be like if you’re not building centralised people farming machinery for Big Tech, have a play with Kitten.
I just whipped up a simple icon search for Kitten Icons (based on Phosphor Icons). It’s not complete yet – I have a few other things to do before I can get back to it today – but you can play with it here:
Screenshot of the work-in-progress icon explorer on the Kitten web site’s reference section showing the icon results for the search term “animal”: Barn, Bird, Butterfly, Cat, Cow, Dog, Fish, FishSimple, Horse, LinuxLogo, PawPrint, Rabbit.
Page content follows after the icon explorer component:
In that namespace, you browse the icons in three ways:
Directly, by name, e.g., kitten.icons.Cat
In categories, e.g., kitten.icons.categories.nature.Cat
Using tags, e.g., kitten.icons.tags.kitten.Cat
The categories and tags objects are authoring-time aids. While you can use them…
I quickly whipped this up using the new generic script in Markdown frontmatter feature I added to Kitten. And I’d imagine this is about the most amount of functionality you’d add using it before refactoring to something more maintainable. (Hey, just because I’m the author of Kitten doesn’t mean I’m not also learning how to use it as build it.) :)
• New: Lovely new icons¹ and new callouts in Kitten Settings²
• New: Markdown now supports attributes and bracketed spans³
• New: client-side kitten global with trigger function for triggering events on the server from the client. (Useful when streaming client-side JavaScript when using Kitten’s Streaming HTML⁴ workflow. e.g., when you have to use a client-only web API like the Clipboard API but you want to keep all your logic on your server-side page.⁵)
• Fixed: The bound render function returned by KittenComponent class’s component getter now correctly awaits asynchronous templates. (In Kitten, you don’t have to care whether your templates contain promises. Kitten handles all that for you.)
Kitten automatically and transparently handles asynchronous content in your templates for you so you don’t have to worry about it. One place where this wasn’t working properly is if you addressed this.component to stream a custom update of your component manually instead of calling the this.update() method of Kitten component instances.
Kitten will have built-in support for the Phosphor icons set with full authoring-time language intelligence where you can search for icons via category and tag (in addition to the canonical alphabetical categorisation).
Thought this was going to take me a few hours but it took a few days thanks to running into issues with size limits, type inference from JavaScript types in modules, etc., with the TypeScript language server but I believe I’ve finally cracked it :)
Screenshot of detail of a Kitten template open in a code editor. Author is editing Kitten component reference in a markdown block, within a H2 header. The caret is at <${kitten.icons.tags. and the autocompletion box is open showing a list of possible fields: a11y, accessibility, accessible, accomodations, account, accounts, accuracy, acrobat, activity, add. The reasty of the line of code reads } weight=duotone size=1.5em/>
👆 Sorry, life got in the way; just deployed this but I haven’t had a chance to document it properly yet. I’ll make a proper announcement when I do.
Also, you can now use the delete attribute on a DOM node/Kitten component you’re streaming back from the server to have it removed from the DOM on the client. It’s syntactic sugar for hx-swap-oob='delete' in htmx.
• Automatic message routing: if the element that triggers an event on the client does not have a name attribute, Kitten now falls back to using its id instead to route the event to the correct server-side event handler on your live Kitten pages.
If neither attribute exists, Kitten will fail to route the message but no longer crash as it was due to a regression introduced when I implemented support for colons in element names.¹
¹ A colon in an element name is ignored for message routing purposes, letting you, for example, give unique names to <details> elements, allowing more than one to be open at a time, while having their events be handled by the same handler.
• Fixes issue with routes where dynamic routes with file names that had more than two extensions were not recognised as the correct type of route. e.g., A route called index.xml.get.js would previously have been treated as a static route instead of a dynamic GET route.
• You can now use key paths in the names of your client-side live components and these will automatically be transformed into object hierarchies on the server for you.¹
• Self heals zombie live pages (see Streaming HTML workflow²) if they return to life due to client-side browser cache.³
• Removes htmx⁴ headers from data property into separate header property in Kitten Page events and the data your Kitten Page message handlers receive.
• Automatically passes references to the live page object (if any) and the request and response objects to the layout templates of Markdown pages⁵ (so you can, for example, check if request.session.authenticated⁶ is true from the your layout template and customise the layout accordingly).
² See Streaming HTML tutorial: https://kitten.small-web.org/tutorials/streaming-html/ (There’s actually more to it now but I haven’t had a chance to document the new class-based and event-driven live page workflow yet. It’s experimental but working very well for me so far so I will do so shortly.)
³ When a person leaves a live/connected page (a page connected to its default web socket), we clean up and remove that live page from memory. However, browsers being what they are, cache the page on the client. If a person uses the back/forward buttons to return to the page, the browser will serve the cached source from memory, which has the old page ID, for the page that no longer exists in Kitten’s memory. So now we have a problem. The only way to recover from this is to tell the page to reload itself. So we accept the WebSocket connection, send a command to the page for it to reload itself, and then close the socket. That makes the stale page self heal by replacing itself with a fresh one. Yay, go us!
• Added remove() method to kitten.Component class. Use this when working with live pages and components and you want to remove a component from the page (or its parent). It will handle removing event listeners for you so you don’t end up with any memory leaks.
• Improved update() method so it similarly removes listeners on child components before updating the component itself in case you have class-based child components that will be reinstantiated on render.
• Updated the send() methods on page.everyone and page.everyoneElse so you can pass a swap target to insert the element being streamed to the page before, after, asFirstChildOf, or asLastChildOf another. (This was already there for the page.send() but now the two broadcast objects have the same consistent interface.
The @small-web/kitten npm package (Kitten’s types package) has also been updated to version 5.1.0 to reflect the latest changes.
(Remember that the new class and event-based page and component model is still experimental and largely undocumented and fully backwards compatible with the classic functional way of authoring your page routes and components.)
A quick demonstration of using the State: Overview page in Kitten’s¹ settings while developing to keep an eye on your event and event listener counts to avoid memory leaks.
Notice how the events and listeners counts change as I navigate between the People and Settings pages in my Place² node and that they are consistent. If they were rising as I navigated back and forth I’d know I had a memory leak somewhere.
If you use Kitten’s built-in features (e.g., the addEventHandler() method on your kitten.Component subclasses, Kitten will handle adding and removing listeners for you automatically during your component’s lifecycle. You can also do so manually in your component’s automatically-called onConnect() and onDisconnect() event handlers.
This view is useful during development to ensure you don’t have any memory leaks as pages are loaded and unloaded.
• Adds swap target to page.send so you can have an element added before, after, as first child of, or as last child of another (this is syntactic sugar over htmx and works around some of the complexities with out-of-band swaps in htmx, especially when streaming table rows to tables).
Also, check out the latest live page and page events state view in Kitten’s Settings (every Kitten app has this settings view).
Rather loving the umpteenth iteration of my streaming setup :)
(Just recorded a preview of Kitten’s improved component model and will share it once it’s uploaded.)
Screenshot of streaming setup showing a MacBook Pro and two other monitors on stands, as well as a Blue brand microphone hovering to the left of a Lego keyboard, an Atem Mini Pro and a Stream Deck. Miscellaneous other little things and papers clutter the background. At the top, the bottom edge of a large LED light with soft diffusion and barn doors is visible, giving a white-ish light. The lighting in the background is primarily blue. The middle monitor is showing the view from the camera, which hovers between the computer and itself. The framed shot shows the further edge of the desk, with an electric keyboard and plants in front of grey curtains, and a red Anglepoise lamp with a warm light adding a bit of depth.
Little preview video: Kitten’s improved component model
• Class-based page routes and components
• Object-oriented
• Event-based
• Seamless hypermedia-driven WebSocket-based event mapping and interface updates (Streaming HTML)
• A light server-side live component hierarchy with event bubbling
• Almost as if you’re building a desktop or mobile app instead of a web app…
… another authoring simplification made possible because on the Small Web – which is a peer-to-peer web – you build a web app/site as a tool for one person (the owner of the site/app) instead of as a tool for you to farm millions of people.
So last night, while recording the preview of Kitten’s¹ improved component model², I made a silly mistake (copying raw HTML into a JavaScript function instead of wrapping it in a kitten.html`` tagged template, easy to do when you’re refactoring to pull out components from pages).
Then, once I figured out what I’d done, I made another one by forgetting to return the value from the function (easy to do when you’re used to using one-line closures as render functions).
I would have caught both of those so much faster if Kitten had helpful error messages for those two pitfalls. And guess what, this morning, it does :)
Attached are screenshot showing the before and after error messages.
Feeling more like myself today after getting my ass kicked by this bug for the past few weeks and I’m finally making progress on improving Kitten’s¹ component model.
Soon, you’ll be able to implement page event handlers in an even simpler way than before. Is your connected DOM element named pixel? Then export an event handler called onPixel() on your page route and it’ll get called when that element triggers its event.
And there’s so much more coming, including a class-based page and component model with DOM-style event bubbling on the server. This should really let you make the most of the Streaming HTML² workflow in Kitten by writing event-driven GUI-like code but for a web app without really caring about the client/server separation.
Need to update the examples, test, test, test, and update docs but this had me stumped for a while now with several false starts and I feel this implementation might just be it 🤞
Draw Together, in ~50 lines of code, is a real-time collaborative drawing tool on a 20×20 pixel grid where people can click to toggle the colour of each pixel.
(The full source and explanations fit the four screenshots on this post.)
Main export
export default () => kitten.html`
<page title='Draw Together'>
<${Canvas} />
<${Styles} />
`
Constructs the main page with the canvas and styles.
Like this? Fund us!
Small Technology Foundation is a tiny, independent not-for-profit.
We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.
Aral Balkan
About the author
I’m an activist, designer, and developer. I’m one-half of Small Technology Foundation, a tiny and independent two-person not-for-profit based in Ireland. We advocate for and build small technology to protect personhood and democracy in the digital network age.
To support my work, become a patron of our foundation.
I’m available for public speaking and media inquiries.