refactor: tlon-aligned agent architecture (UI core, abed peek/watch, linear migrations)#4
Merged
Merged
Conversation
… core Two related changes: 1. lib/notes-ui.hoon is now a |% core. The +index arm holds the HTML (regenerated from app/notes-ui/index.html via the new scripts/build-notes-ui.sh). +manifest, +service-worker, +favicon-svg, +icon-svg moved out of app/notes.hoon into this core. Agent imports as `/= ui /lib/notes-ui` and references arms as `index:ui`, `manifest:ui`, etc. 2. The %handle-http-request branch of +poke is lifted into a top-level ++serve-http arm (named to avoid shadowing the http namespace used by `response-header:http`). +poke now just delegates with a one-liner. Net effect on app/notes.hoon: -45 lines. scripts/build-notes-ui.sh splices index.html into the ++index arm body without touching the static asset arms, so manifest/sw/svg can be hand-edited independently. Triple-quote safety check kept. AGENTS.md (CLAUDE.md symlinks to it) updated for the new workflow and to switch the default dev ship reference from bospur to sidwyn. Tests: 53/53 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mass-rename type references from :notes to :n by changing the import declaration in each consumer: /- notes → /- n=notes flag:notes → flag:n state-9:notes → state-9:n ... Affects desk/app/notes.hoon, desk/lib/notes-json.hoon, desk/tests/app/notes.hoon, and the four mark files under desk/mar/notes/. Two collision fixes in the test suite where local face names shadowed the import: - ++nb-flag took `n=@ud` for the count, masking the n alias for the ^- flag:n cast. Renamed the parameter to `nid`. - ++ex-history-len took both `nid=@ud n=@ud` for the same reason. Renamed the count parameter to `expected`. Tests: 53/53 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move per-notebook peek and watch logic into arms inside the per-notebook cores, so the top-level dispatchers shrink to flag-parse + delegate. The notebook-state, flag, members, etc. are loaded once at abed time and visible to all the inner arms. - ++no-peek (in no-core) — handles all 7 per-notebook scry paths (%notebook, %folders, %notes, %note/<id>, %note-history/<id>, %folder/<id>, %members). Top-level +peek catches them with a single [%x %v0 kind=@ ship=@ name=@ rest=*] arm. - ++no-watch (in no-core) — handles the local UI %stream subscription. - ++se-watch (in se-core) — handles remote-subscriber %updates watch (delegates to existing ++se-watch-sub). - ++no-abed relaxed: no longer asserts ?=(%sub -.net), so it works for any net type. The %sub assertion moved into ++no-action, ++no-start-watch, ++no-leave, ++no-agent — the write/sign arms that actually require subscriber semantics. ++no-response left without an outer guard because adding one breaks type narrowing on the %update branch. Cross-cutting peeks (%notebooks, %published, %invites, /x/ui, /x/debug/dummy) and watches (%inbox %stream) stay at the top level. Tests: 53/53 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two structural moves in the helper core, no logic changes:
1. Wrap +poke's body in a |^ kelt so the six poke-only handler arms
(join-remote, leave-remote, handle-{send,notify,accept,decline}-
invite) live inside +poke instead of polluting the outer subject.
The kelt arms still reach the outer subject (state, cor, give-inbox-*,
se-core, no-core) since |^ extends rather than replaces it.
2. Move the nine utility arms to a contiguous block just before
++se-core, in this order:
slugify, a-notebook-to-c-notebook, get-book, strip-query,
can-view-flag, find-flag-by-nid, notebooks-changed-card,
give-inbox-received, give-inbox-removed.
Net effect on +helper-core arm order: dispatchers (init, load, poke,
serve-http, watch, peek, agent, arvo) up top, utilities below them,
per-notebook cores at the bottom — so a reader sees the entry points
before the helpers.
Tests: 53/53 backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restructure +load to follow the tloncorp/homestead groups.hoon shape: a
|^ kelt with a +$ any-state head-tagged union and one ++state-N-to-N+1
arm per version step. Replaces the prior "jump to state-8" cascade
where states 1–7 each migrated directly to state-8 in a deeply nested
?: chain.
++ load
|^ |= =vase
^+ cor
=+ !<(old=any-state vase)
=? old ?=(%1 -.old) (state-1-to-2 old)
=? old ?=(%2 -.old) (state-2-to-3 old)
...
=? old ?=(%9 -.old) (state-9-to-10 old)
?> ?=(%10 -.old)
=. state old
cor
+$ any-state $%(state-10:n state-9:n ... state-1:n)
++ state-1-to-2 |= s=state-1:n ^- state-2:n [...]
...
--
Each step is small and focused (5–30 lines) with a `~> %spin.[...]`
trace label. Cumulative 1 → 10 transformation matches the prior
cascade. The legacy v0/v8 entity types and intermediate state types
remain in sur/notes.hoon (used by the test suite to seed each starting
version).
Net: -39 lines in app/notes.hoon (-193 of the old cascade, +154 new).
Tests: 53/53 backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scry endpoints used to all return %json+!>(...). Now each returns a typed mark with both noun and json grow arms, so callers can scry the same path and get either the raw noun (e.g. via %notes-notebooks) or JSON (.json extension on Eyre, or %json mark coercion). 10 new mark files in mar/notes/: - notebook — wraps notebook-detail (flag + notebook record) - notebooks — list of notebook-summary (flag + notebook + visibility) - folder — wraps folder - folders — list of folder - note — wraps note - notes — list of note - note-history — list of note-revision - members — list of member-record (ship + role) - invites — list of invite-record (flag + invite-info) - published — list of published-record (flag + note-id + html) 5 listing wrapper types added to sur/notes.hoon (notebook-summary, notebook-detail, member-record, invite-record, published-record). JSON encoders for the new types added under enjs:notes-json (notebook-detail, notebook-summary, notebook-summaries, member-record, member-records, invite-record, invite-records, published-record, published-records). ++peek and ++no-peek now build the noun-typed value first, then return it via the typed mark — Eyre's grow:json handles JSON on the wire. "Not found" cases switched from json+!>(~) to outer-null `~`, the idiomatic absent-scry response. Test peek helpers updated: ++ex-mark asserts the new mark; ++peek-history binds (list note-revision:n) instead of (list json); ++ex-gone simplified to outer-null check. Tests: 53/53 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit pass: every =/ that just renames a wing (e.g. =/ nid=@ud id.nb-act) is reconsidered: - 12 multi-use + verbose aliases switched to =* (the proper aliasing rune): n-act, who, fid in se-rename-folder/se-move-folder/ se-delete-folder/se-create-note, nid in se-rename-note / se-move-note / se-delete-note / se-update-note / se-restore-note, nid-nb in se-batch-import-tree. - 15 aliases inlined: short or single-use (nb-act inlined since a-notebook.act is only 14 chars; u-nb in no-apply-update — 11 uses, but u-notebook.upd is 14 chars so still better inlined; title in handle-send-invite at 1 use; etc.). - 1 dead-code =/ deleted (fid in se-dispatch-folder, never referenced; the dispatch arm passes cmd wholesale). Computed values (function calls, literals) keep =/ — they're not pure aliases. The =/ =face:type source style (e.g. =/ =flag:n flag.act) also stays since the =face syntax is a face-mold cast, not aliasing. Net: -16 lines in app/notes.hoon. Tests: 53/53 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the rule: use the natural type name as the face when the type is short or used in only 1-2 places; only deviate when a real shadowing collision forces it. Renames in app/notes.hoon (no logic changes): - new-nb / placeholder-nb / nb (×2) → notebook - nt (×12 sites in se-create-note, se-rename-note, se-move-note, se-update-note, se-delete-note, se-restore-note, batch-import arms, loop bodies, scry binding) → note - nf (folder, ×2) → folder - nf (flag, ×3 in state-9-to-10 migration) → flag - f=flag:n in se-abed / no-abed gate samples → flag Wing resolution (right-to-left dot lookup) means notebook.notebook-state still finds the .notebook field of notebook-state correctly even with a local face named notebook. The slight stutter in lines like se-core(flag flag, ...) is acceptable per the rule — readability of the type name wins over avoiding stuttery wing-replace syntax. Tests: 53/53 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hoon's =type syntax auto-creates a face matching the type name, so `=/ flag=flag:n ...` is just `=/ =flag:n ...`. Same for =notebook:n, =note:n, =folder:n in the spots where the previous pass renamed abbreviations to their natural type names. 19 sites updated. Tests: 53/53 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two style fixes across the helper-core arms:
1. ++se-rename-notebook had the read-mutate-write triplet:
=/ =notebook:n notebook.notebook-state
=. notebook notebook(title ..., updated-at ..., updated-by ...)
=. notebook.notebook-state notebook
Three lines for one in-place field update. Collapsed to a single
=. notebook.notebook-state ...notebook.notebook-state(...) write
using %_ for the multi-field block. No alias, no copy.
2. 21 arms had ?> ?=(...) before ^+ se-core, opposite of the convention
(cast right after the gate sample, then assertions). Swapped order in
se-create-notebook, se-rename-notebook, se-delete-notebook,
se-set-visibility, se-invite, se-member-{join,leave},
se-dispatch-folder, se-dispatch-note, se-create-folder,
se-{rename,move,delete}-folder dispatch arms,
se-create-note, se-{rename,move,delete,update,restore}-note
dispatch arms, plus a few more.
Tests: 53/53 backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cells Two stylistic rules applied across the helper core: Rule 1 — when typing a =/ as a tuple, use * for parts not accessed at that site: - two %notes-action dispatch arms (poke %note branch + main): only -.net.entry is checked, so entry=[=net:n *] - handle-send-invite: only title.notebook.notebook-state.entry is read, so entry=[* =notebook-state:n] - the /v0/notebooks peek gate sample: only notebook-state half used - the find-flag-by-nid gate sample: only notebook-state half used - the state-9-to-10 xlat builder: only notebook-state half used Rule 2 — same-subject cells collapse to [a b c]:subject when the cell is the LAST element of the outer expression (right-associative noun shape splices through): - (slugify title.notebook.notebook-state id.notebook.notebook-state) → (slugify [title id]:notebook.notebook-state) - /v0/notebooks listing item: [flag notebook.notebook-state visibility. notebook-state] → [flag [notebook visibility]:notebook-state] - se-create-notebook constructions: [...our.bowl now.bowl now.bowl our.bowl] → [...[our now now our]:bowl] (notebook + root folder) - se-create-folder construction: bowl tail collapsed - se-batch-import-tree folder: bowl tail collapsed Note constructions (se-create-note, batch-import, batch-import-tree notes) tried-and-reverted: revision=@ud follows the bowl block, so the bowl-cell is NOT the last sub-expression and the right-associative splice doesn't apply. Left as-is. Tests: 53/53 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The %notes-action branch of +poke was a chain of ?: ?=(%X -.act) tests
ending in a ?> %notebook fall-through, with another nested chain inside
the %notebook case for %invite/%note/default-route, and a third nested
chain inside %note for %publish/%unpublish/default-route.
Restructured to:
?. ?=(%notebook -.act)
:: switchable cases — %notebook is the meaty fall-through
?- -.act
%create-notebook ...
%join ...
%leave ...
%accept-invite ...
%decline-invite ...
==
:: meaty %notebook body
=/ =flag:n flag.act
?+ -.a-notebook.act
:: default: route to host (se/no) based on net
...
::
%invite
(handle-send-invite ...)
::
%note
?+ -.n-act
:: default: route
...
::
%publish
:: local
::
%unpublish
:: local
==
==
Default-routing logic appears in two places (outer ?+ default and inner
%note ?+ default) — kept the duplication; small enough.
Tests: 53/53 backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
Follow the +go-a-group pattern from tloncorp/homestead/groups.hoon: the action handler converts to a c-notes command and pokes the host unconditionally. If host == our.bowl, Gall loops the poke back through +poke %notes-command and dispatches to se-core. Removes the host/sub discriminator from the action surface. Agent changes: - +poke %notes-action default cases (both outer ?+ and inner ?+ for %note) drop the `?: ?=(%pub -.net.entry)` branch. Each just calls no-abet:(no-action:(no-abed:no-core flag) act). - ++no-action drops the `?> ?=(%sub -.net)` assertion. Other no-* arms keep their %sub guards (those are genuinely sub-only). Test harness changes (lib/test-agent.hoon): - ++drain-self-pokes + ++drain-loop helpers detect any [%pass wire %agent [our.bowl <our-dap>] %poke =cage] cards in the emitted list and re-deliver them through on-poke. Bowl state is threaded via play-cards so wex/sup remain coherent. Recursion has a depth-16 cap. - ++do-poke and ++do-agent run the drain after the initial agent call, so test assertions on state see the fully-applied result, not just the queued self-poke card. Tests: 53/53 backend (no test changes needed — the drain is transparent). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AGENTS.md still described state-4 (state-0..4 in sur, current state-4) from before the migration cascade was rebuilt. Updated the State and API Surface sections to match what's actually deployed: - Current state is state-10, with shape books/next-id/published/invites - flag is [=ship name=@tas] with slugified names - notebook-state contains notebook/members/visibility/folders/notes/history - Migrations follow the tlon |^ kelt + linear =? chain pattern - Scry endpoints now return typed marks (notes-notebooks, notes-note, etc.) with both noun and json grow arms - Action surface routes through no-action which always pokes the host (Gall loops self-pokes back through %notes-command); only %publish / %unpublish stay local - HTTP routes lifted into ++serve-http - Watch arms split: ++no-watch for local UI, ++se-watch for remote subs For /v0/published, the published-record type carried html=@t but the encoder never emitted it — the listing has only ever returned host / flagName / noteId pairs in JSON (which is what the FE consumes). Dropping html from the type so the noun-form mark also reflects the documented shape; saves the wasted bandwidth on the noun-encoded peek. Tests: 53/53 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The %publish / %unpublish action handlers used to mutate published.state
directly with no checks — any local poke could publish HTML keyed
under any flag, including subscribed remote notebooks. Tightened to:
- new ++publish-note / ++unpublish-note arms in cor (since published is
in outer state, not notebook-state). Each arm:
?> =(src.bowl our.bowl) — local-action only
?> =(ship.flag our.bowl) — host-only (only the host's ship
maintains its public URL)
abed se-core to get notebook context
?> (se-can-edit:se src.bowl) — edit permission
publish also asserts the note exists
- action handler delegates: (publish-note flag nid html) /
(unpublish-note flag nid).
New tests:
- test-publish-note-rejects-non-host (~bus tries to publish under
~zod's flag → crash)
- test-publish-note-rejects-missing-note (publish a non-existent note
id → crash)
Tests: 55/55 backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inbox events were the last surface emitting raw json+!>(...) cards;
every other client surface now uses typed marks. Consistency fix.
- New +$ u-inbox tagged union in sur/notes.hoon — three variants:
%invite-received, %invite-removed, %notebooks-changed.
- ++u-inbox encoder under enjs:notes-json reproduces the existing
{type:'update', update:<payload>} wire shape so the FE's
applyNotebookUpdate / applyInboxEvent dispatch keeps working.
- New mar/notes/inbox-update.hoon with grad %noun and grow noun+json.
- ++give-inbox-received / ++give-inbox-removed / ++notebooks-changed-card
emit notes-inbox-update+!>(<u-inbox value>) instead of building the
JSON inline.
Tests: 55/55 backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Master added several functional improvements between when this branch forked and now. Reconciled them onto the refactored layout: - v0.10.1 / v0.11.0 UI work (drafts in localStorage, faster autosave, preview shows title, list/task editing UX, checkable preview checkboxes, suppress join modal on already-joined notebooks): merged via auto-merge of index.html; lib/notes-ui.hoon regenerated via scripts/build-notes-ui.sh. - leave-remote: emit %member-leave to host before unsubscribing (so the host's members.notebook-state reflects reality). Ported into the |^ kelt arm. - /v0/notebook scry: now includes visibility. Updated notebook-detail type in sur and the encoder; the no-peek %notebook arm builds [flag notebook visibility]. - +agent: handle [%notes %leave ship name ~] poke-ack as a no-op (best-effort). - +arvo: handle [%behn %wake] for [%notes %rewatch ship name ~] — re-arms a subscription after a transient failure. - ++no-agent watch-ack failure: instead of silently dropping the sub, schedule a 30s rewatch via behn timer. - Syncer (Tauri menubar app) v0.2.0 alignment with state-10 agent. Tests: 55/55 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ioning The previous publish-note / unpublish-note arms in cor were checking host ownership and edit permission via se-core — too much. The action poke layer already enforces ?> =(our.bowl src.bowl), and no-abed already validates that the notebook exists. Beyond that, publish is just a per-ship cache write (the local /notes/pub/<flag>/<id> URL serves whatever this ship caches), not an authority assertion that needs the host's edit-role gate. Replaced with two arms in no-core: ++ no-publish |= [nid=@ud html=@t] :: write published.state ++ no-unpublish |= nid=@ud :: del Action handler dispatches via no-abet:(no-publish:(no-abed:no-core flag) ...). no-abed crashes if the flag isn't in books; the action poke layer crashes on non-self src. That's the full check set. Tests: - test-publish-note-rejects-non-host renamed to *-rejects-non-self (the rejection is the action-poke self-check, not a ship-flag check). - test-publish-note-rejects-missing-note replaced with test-publish-note-rejects-unknown-notebook (no-abed rejects unknown flags; we don't validate that the note-id exists within the notebook anymore — published is a key-value cache). Tests: 55/55 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The agent-door header has =| current-state followed by =* state -, so the agent state value sits at the head of the subject. Wing resolution walks into it for free, which means books, next-id, published, invites each resolve directly without the .state suffix. 52 sites updated across the helper core: books.state → books, etc. The local-face .s in migration arms (state-N-to-N+1) is unrelated and stays — that's the migration input, not the agent state. Tests: 55/55 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The host's self-poke from the action handler comes back as %poke-ack on the [%notes %sub ship name ~] wire — same wire used by genuine subscriber agent signs. The outer ?> ?=(%sub -.net) at the top of no-agent crashed the host because its net is %pub. Move the sub-only check into the per-sign-type branches that actually need it: - %fact: route to no-response unconditionally (no-response itself already short-circuits when the host emits to itself, since we don't watch our own /updates wire). - %kick: only re-watch if we're a sub; host doesn't watch self. - %watch-ack: only re-arm the rewatch timer if sub. - %poke-ack (default): always no-op. Surfaced by an action poke crashing on sidwyn with `take %poke-ack failed at no-agent` — host's self-poke ack was tripping the assertion. Tests: 55/55 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two FE bugs surfaced while exercising the e2e flows: 1. ++triggerAutoCreate captured the user's pending title/body once early (right after the create-note poke) and restored them after several awaits (loadNotes, selectNote). Any keystrokes typed during that ~150-400ms window were silently dropped on restore — symptom: typing a title quickly then switching to body sometimes "snapshotted" an empty body. Fix: capture pending values just before selectNote. 2. channelId was initialized to "" and only set inside ++openChannel, which runs from ++connect. Fast user clicks (or e2e tests) firing a poke before connect() finished produced PUT /~/channel/ (empty id) → 400. Fix: initialize channelId at module load with a Date.now() id; the channel comes into being on the first PUT, so the name is just a label we pick eagerly. Bumped ++dummy to v0.11.0-refactor. Tests: 55/55 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same race shape as the channelId one — SHIP is fetched via /~/name during ++connect, and pokes that fire before that resolves carry ship:"" → Eyre 400. - Expose window.__notesGetShip() returning the current SHIP value once /~/name has resolved. - Test fixture (notes + openSubscriberContext) waits up to 15s for the probe to return a non-empty ship before yielding the page to the test body. Tests: 55/55 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a cross-ship-invite test fails before the sub accepts/declines, the entry stays in the sub's invites.state and accumulates across runs. Same for cross-ship-decline-invite if it fails before the decline. - wipe-leftovers: scry /v0/invites for each ship; if any e2e- titled invite is present, navigate the page to /notes/ and call declineInvite() via the FE. - tryDelete (per-test cleanup): sweep matching invites for the title before attempting the notebook deletion. Best-effort, errors swallowed. Tests: 55/55 backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ergonomics for diagnosing flaky cross-ship behavior — when a sub edit
doesn't appear on the host, you usually want to know what SSE events
the FE actually received. Adds:
- handleEvent gates a `console.log("[sse]", JSON.stringify(data))` on
localStorage.e2e-log-sse === "1". Off by default (no-op for users).
- Test fixture sets the localStorage flag on every page (host + sub),
and pipes browser console / pageerror through to the test runner —
but only when E2E_TRACE=1 is set, so green runs stay quiet.
Usage: `E2E_TRACE=1 npm run test:e2e -- --grep 'cross-ship edit'`
Tests: 55/55 backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cdba5ff to
4a76627
Compare
Playwright UI's Console tab auto-captures browser console.log output per action — but only if the FE actually logs. Gating the localStorage flag on E2E_TRACE=1 meant UI mode showed nothing. Always set the flag now; E2E_TRACE=1 still gates the runner-side pipe (attachConsole) for CLI runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
handleEvent only sees fact/snapshot/inbox messages — poke-ack short-circuits before it. Move the trace log up to eventSource.onmessage so every raw SSE payload (including poke-acks) is captured. Both layers now log when localStorage.e2e-log-sse === "1". Bumped ++dummy to force a reload. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
forceSave's "Saved" indicator only means the FE's local poke-ack came back — for cross-ship subscribers, the host's se-update and the fact's trip back through the stream subscription happen AFTER that. Without a settle, cross-ship-edit asserts on the host's editor before the update event has been delivered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Host was already in the notebook from the create+invite steps and is subscribed to its /stream — the sub's create-note should propagate via the existing subscription. The page.goto + selectNotebook re-navigation masked any real propagation bug in the original test. If this fails now, the failure is the actual stream-propagation bug we want to surface, not a workaround. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arthyn
added a commit
to tloncorp/hoon-reference
that referenced
this pull request
May 7, 2026
Two more rules from arthyn/notes#4 follow-up commits (68e0e1a, b6b8d48). architecture.md - New "Always poke the host" subsection in ACUR: the action handler emits a poke to ship.flag unconditionally; Gall loops self-pokes back through +poke %notes-command. Removes the host/sub branch from the action layer entirely. c-command processing becomes the single dispatch point for all state-changing logic. - New "State fields resolve without .state" callout under standard boilerplate: =| / =* state - puts state at subject head, so wing search finds books, invites, etc. directly. Prefer the unprefixed form except where local shadowing forces it. patterns.md - Updated existing examples (Tuple Types with *, Per-Entity Engines) to use books / cor(books ...) instead of books.state / cor(books.state ...) for consistency with the new rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Marks the refactor cycle on PR #4: tlon-aligned dispatch (action → no-action → command → host), typed marks for every scry endpoint, linear migrations, ?- / ?+ dispatch over chained ?:, =* aliasing, =type face shortcut, no-publish/no-unpublish through the engine, inbox-update mark, and a handful of e2e harness fixes (drain self pokes, settle after forceSave, eager channelId, wait for SHIP). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Five focused structural refactors to align
app/notes.hoonwith tloncorp idioms. No semantic changes; backend tests stayed 53/53 green at every step.03ccf8bExtract HTTP handling into++serve-http; move PWA static assets (manifest, sw, favicon-svg, icon-svg) into a|%core inlib/notes-ui.hoon. Newscripts/build-notes-ui.shregenerates the++indexarm fromapp/notes-ui/index.htmlwithout touching the static asset arms.c1a6080Mass-rename type imports:/- notes→/- n=notes; all:notesrefs in agent / lib / tests / mark files become:n.8bc56dePer-notebook peek/watch via abed:++no-peek,++no-watchin no-core;++se-watchin se-core. Top-level+peekcollapses to a generic[%x %v0 kind=@ ship=@ name=@ rest=*]delegate.++no-abedrelaxed (works on any net);?=(%sub -.net)guard pushed into write/sign arms.90ceaacWrap+pokein a|^kelt — six poke-only handlers (join-remote,leave-remote,handle-{send,notify,accept,decline}-invite) nest inside+poke. Move 9 utility arms (slugify,strip-query,get-book,can-view-flag, etc.) to a contiguous block just before++se-core.82de44fRestructure+loadto follow the homestead/groups pattern:|^kelt with+$ any-statehead-tagged union and one++state-N-to-N+1arm per version step (1-to-2, 2-to-3, ..., 9-to-10). Replaces the prior nested?:cascade where states 1–7 jumped directly to state-8.Test plan
mcp__sidwyn__run-tests desk=notes path=/tests/app→ 53/53 OK at every commit🤖 Generated with Claude Code