Skip to content

docs(scoped-atoms): data tables as the page's examples - #46

Merged
jarrednorrisdev merged 1 commit into
mainfrom
docs/scoped-examples
Oct 10, 2026
Merged

jarrednorrisdev merged 1 commit into
mainfrom
docs/scoped-examples

Conversation

@jarrednorrisdev

@jarrednorrisdev jarrednorrisdev commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Reworks the scoped-atoms docs page around one realistic example: data tables on an admin page. Builds on #45, now merged. Rebuilt as a single commit on main.

Background: a scoped atom gives each instance of a component its own atom: the component calls Table.provide(), and any component below it reads that atom with Table.use(), like Svelte context. The page used two note editors for this, which raised more questions than it answered ("why not a family keyed by note id?", "what app has two editors mirroring each other?"). A data table answers them: its toolbar, header, rows and footer are separate components sharing one search, sort, page and selection, and an admin page often has several tables.

Example 1: two tables on one page

<!-- admin.svelte -->
<DataTable columns={orderColumns} descending name="Orders" rows={orders} sort="date" />
<DataTable columns={customerColumns} name="Customers" rows={customers} sort="name" />
<!-- data-table.svelte: each table provides its own state -->
const table = Table.provide({ columns, descending, name, rows, sort });

<!-- table-toolbar.svelte, table-header.svelte, table-rows.svelte, table-footer.svelte: no props -->
const table = useAtom(Table.use());

Sorting, searching, selecting or paging in one table leaves the other alone. The page shows this in a pretend browser window (the kit's BrowserFrame). An X-ray button outlines each table that provides its own state, tagged Orders: Table.provide(), and each table's atom is labelled by its table, so the page's atom graph shows "Orders table" and "Customers table".

A note under the example answers "why not a family keyed by a table id?": a family needs someone to invent unique ids and pass them to every part, while a scoped atom's identity is the table component itself and its state lives exactly as long as the table. A family fits when the state should belong to the id, such as a sort remembered for the Orders table after navigating away.

Example 2: line items inside an order (nearest provider)

<!-- order-items.svelte -->
{#snippet lineItems(order: Row)}
  <DataTable columns={itemColumns} name="Line items" rows={itemsOf(order)} sort="name" />
{/snippet}

<DataTable details={lineItems} expanded="#1042" name="Orders" … />

An order expands to show its line items as a smaller table inside the Orders table, so both provide Table. The line items' parts call the same Table.use() and get the line items' state, the nearest provider's. Collapsing the order and expanding it again starts its table afresh, because the table remounted.

Also on the page

  • "Defining" and "Providing and using it" now show Table instead of Draft.
  • "Scoped atoms or families?" opens with the rule: state that belongs to something with an id is a family's, and state that belongs to an instance of a component is a scoped atom's.
  • A new "Using both" section covers combining the two: a table that remembers its search, sort and page by name keeps that state in a family (kept alive), and its scoped atom provides the family's atom, so the parts still call Table.use(). The snippet says it's a variant of Table that takes the table's name instead of its rows. The family decides identity and lifetime; the scoped atom decides who can reach it. It ends with a pointer to server-rendered data, the most common case for the combination.
  • "Values from the server" moves to the Hydration page, as a "Scoped atoms" subsection under Serializable atoms. Scoped atoms comes before the async and server pages in the sidebar, so its readers hadn't met serialization keys yet. The section is rewritten as a numbered sequence (two comments by one author, two atoms with one key, the server throws), then the fix. The troubleshooting entry for the duplicate-key error links to its new place.
  • One sentence says the pattern isn't specific to tables: any widget made of parts that share state (a player and its controls, a form and its fields).

Tests

  • e2e/demo.test.ts, "each table provides its own state": sorting, searching, selecting and paging in one table leave the other alone, and the X-ray tags each table.
  • e2e/features-atoms.test.ts, "use finds the nearest provider": the line items keep their own selection and sort inside the Orders table, and start afresh after the order is collapsed and expanded again.

Locally, both pass in Chromium, and the two shell tests that load this page pass in Chromium and WebKit. The page has no console errors in light or dark mode, at desktop or phone width. Format, lint and type checks pass.

Decisions

  1. Data tables, after several rejected examples. Note editors, feed composers and dashboard cards each staged a situation no real app has. Tables need no staging: several on one admin page, and a nested table in an expanded row, are both common.
  2. The table logic is hand-rolled and synchronous. It's a stand-in so the page stays about scoping: no table library (real apps might use TanStack Table), and no server loading, which belongs on the Async atoms page.
  3. One scoped atom per table, holding its rows and its view state together. Splitting the rows into a second scoped atom would be purer, but it doubles the scoped-atom code on the page that introduces them.
  4. X-ray outlines only the providing tables, not every part: the outline makes it clear enough which table each part belongs to.
  5. Server data is explained on the Hydration page, not here. The page now uses only what the sidebar has taught by that point.
  6. BrowserFrame can now frame a single page with a fixed address, beside its existing multi-page mode.
Where to look in the diff
  • apps/demo/src/routes/scoped-atoms/:
    • table-scope.ts: the Table scoped atom, and visibleRows.
    • data-table.svelte: the table that provides it; table-toolbar.svelte, table-header.svelte, table-rows.svelte and table-footer.svelte are its parts.
    • admin.svelte and order-items.svelte: the two examples, with admin-data.ts.
    • site.svelte and xray.svelte.ts: presentation only (the browser window and the X-ray), outside the shown code.
    • +page.md: the prose.
  • apps/demo/src/routes/hydration/+page.md: the new "Scoped atoms" subsection; troubleshooting/+page.md: its link.
  • apps/demo/src/lib/docs/kit/browser-frame.svelte: the optional address.
  • apps/demo/e2e/demo.test.ts and features-atoms.test.ts.

🤖 Generated with Claude Code

@jarrednorrisdev jarrednorrisdev changed the title docs(scoped-atoms): composers, close and reopen, and when to use a family docs(scoped-atoms): data tables as the page's examples Oct 9, 2026
Base automatically changed from docs/scoped-family to main October 10, 2026 00:29
The scoped-atoms page's examples are now an admin page's tables: Orders and Customers each provide
their own state with Table.provide(), and their toolbar, header, rows and footer find it with
Table.use(). An order expands to show its line items as a table inside the Orders table, for the
nearest-provider section. An X-ray outlines each providing table. A note says when a family keyed
by a table id fits instead, and a new "Using both" section shows a family provided by a scoped
atom. "Values from the server" moves to the Hydration page, rewritten. BrowserFrame can frame a
single page with a fixed address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jarrednorrisdev
jarrednorrisdev marked this pull request as ready for review October 10, 2026 00:30
@jarrednorrisdev
jarrednorrisdev merged commit fc7c6da into main Oct 10, 2026
16 checks passed
@jarrednorrisdev
jarrednorrisdev deleted the docs/scoped-examples branch October 10, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant