2. Boundaries and the List
What we want
Section titled “What we want”A list of notes on the page. Each note wrapped in a <kit-boundary> so future events from inside that note carry the note’s identity automatically.
A starter set of notes
Section titled “A starter set of notes”Create src/notes.ts:
export type Note = { id: string title: string body: string updatedAt: number}
export const seedNotes: Note[] = [ { id: 'n_welcome', title: 'Welcome to Quill', body: 'A small notes app, built without React.', updatedAt: Date.now() - 1000 * 60 * 60 * 24, }, { id: 'n_design', title: 'Design tokens', body: 'Components should consume CSS custom properties.', updatedAt: Date.now() - 1000 * 60 * 60, }, { id: 'n_links', title: 'Links', body: 'Native dialog: developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog', updatedAt: Date.now() - 1000 * 60 * 5, },]A render function
Section titled “A render function”src/render.ts:
import type { Note } from './notes.js'
export function renderNoteList(notes: Note[]): string { return ` <kit-boundary surface="note-list" feature="notes"> <ul class="note-list"> ${notes.map(renderNoteItem).join('')} </ul> </kit-boundary> `}
function renderNoteItem(note: Note): string { return ` <li> <kit-boundary surface="note-card" entity-type="note" entity-id="${escape(note.id)}"> <article class="note"> <h3>${escape(note.title)}</h3> <p>${escape(note.body)}</p> <small>Updated ${new Date(note.updatedAt).toLocaleString()}</small> </article> </kit-boundary> </li> `}
function escape(value: string): string { return value.replace(/[<>&"']/g, (c) => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''', }[c]!))}Two boundaries:
- The outer
<kit-boundary surface="note-list" feature="notes">declares the surface and feature. - Each
<kit-boundary surface="note-card" entity-type="note" entity-id="...">declares the specific note. Surfaces stack, so events emitted inside a card carry bothnote-listandnote-cardinsurfaces, with the deeper one as the primarysurface.
Wire it into main.ts
Section titled “Wire it into main.ts”Update src/main.ts:
import '@atheory-ai/kitsune-app'import '@atheory-ai/kitsune-ui'import { debugModule } from '@atheory-ai/kitsune-dev'import { dialogModule, notificationModule } from '@atheory-ai/kitsune-ui'
import { seedNotes } from './notes.js'import { renderNoteList } from './render.js'
await customElements.whenDefined('kit-shell')
const shell = document.querySelector('kit-shell')!shell.modules = [notificationModule(), dialogModule(), debugModule()]
document.getElementById('main')!.innerHTML = renderNoteList(seedNotes)A little CSS
Section titled “A little CSS”Add to src/quill.css:
.note-list { display: grid; gap: 0.75rem; list-style: none; margin: 0; padding: 0;}
.note { background: light-dark(oklch(98% 0 0), oklch(25% 0 250)); border: 1px solid light-dark(oklch(85% 0 0), oklch(35% 0 250)); border-radius: 0.5rem; padding: 0.875rem;}
.note h3 { margin: 0 0 0.25rem; font-size: 1rem; }.note p { margin: 0 0 0.5rem; }.note small { color: light-dark(oklch(45% 0 0), oklch(70% 0 0)); }What we have now
Section titled “What we have now”Three notes render to the page. The boundaries don’t do anything yet — but the moment any element inside emits an event, it’ll come out with full context attached. The list is ready for behavior.
You can verify by running this in the console:
document.querySelector('kit-boundary[surface=note-card]').context// → { surfaces: ['app', 'note-list', 'note-card'], surface: 'note-card', feature: 'notes', entity: { type: 'note', id: 'n_welcome' } }The DOM tree carries the meaning.
What’s next
Section titled “What’s next”Make the cards clickable. We’ll use kit-card, declare meta-event for an “open note” interaction, and watch the event carry surface, feature, and entity all on its own.