DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path inspection-briefs/loomworks-list-loading-cr-2-step0-report-v0_1.md

Loomworks list-loading — CR-2 Step-0 inspection report — v0.1

Document: loomworks-list-loading-cr-2-step0-report-v0_1 Status: complete — READ-ONLY inspection Date: 2026-06-27 By: Claude Code (CC) on DUNIN7-M4 For: the CR-2 (frontend half of list-loading) drafting chat Repos read: loomworks-engine @ eeae9c2 (merged main, alembic 0096 — CR-1 landed) · loomworks (Operator Layer) branch feat-companion-surface @ 17263c9

> Read-only. No edits, no commits, no migrations. Confirms the live facts CR-2's draft depends on, against the merged CR-1 contract (not the CR's restatement) and the built OL frontend.


Bottom line up front


A. The merged contract — live param + field names (engine eeae9c2)

Request params — identical across all ten: limit: int + cursor: str | None. Pass a response's next_cursor back as cursor. (limit ceilings differ: ≤100 for engagements/considerations/compositions/shape-events/assertion-groups/conversation-history; ≤200 assertions; ≤500 manifestations/shapings/renders — default 50 everywhere.)

Response fields — identical spelling across all ten: has_more: bool, total_count: int, next_cursor: str | null.

The one per-endpoint difference — the item-array key:

| Endpoint | Request | Response item key | has_more / total_count / next_cursor | |---|---|---|---| | GET /engagements | limit, cursor | engagements | ✓ ✓ ✓ | | GET /engagements/{id}/considerations | limit, cursor | considerations | ✓ ✓ ✓ | | GET /engagements/{id}/compositions | limit, cursor | items | ✓ ✓ ✓ | | GET /engagements/{id}/shape-events | limit, cursor | shape_events | ✓ ✓ ✓ | | GET /engagements/{id}/assertion-groups | limit, cursor | assertion_groups | ✓ ✓ ✓ | | GET /engagements/{id}/manifestations | limit, cursor | manifestations | ✓ ✓ ✓ | | GET /engagements/{id}/shapings | limit, cursor | shapings | ✓ ✓ ✓ | | GET /engagements/{id}/assertions | limit, cursor | assertions | ✓ ✓ ✓ + vestigial limit | | GET /engagements/{id}/renders | limit, cursor | renders | ✓ ✓ ✓ | | GET /operator/conversation-history | project_id, limit, cursor | turns | ✓ ✓ ✓ |

Flags for CR-2:


B. Home-lens wiring — the Step-0d open question, RESOLVED

**The home consumes /me/dashboard/* (the paginated section endpoints), NOT /operator/dashboard.**

Consequence (the decisive answer): the home's two dashboard lenses hit /me/dashboard/needs_you + /me/dashboard/recent — the Group-A endpoints that were already keyset-conformant before CR-1 (they ship total_count + next_cursor). So the engine already pages the home lenses. But the frontend adapter discards the cursor — e.g. fetchInboxItems returns wire.items.map(...) and drops next_cursor/total_count (dashboard.ts:72–74). The SubstrateNeedsYouResponse type even declares next_cursor: string | null (dashboard.ts:53–57) — it's parsed and thrown away.

CR-2 for the home lenses is a frontend re-wire (consume the existing next_cursor, render load-more), not from-scratch engine work. The "Your engagements" list (GET /engagements) is a CR-1 endpoint and also pages now — same re-wire shape.


C. Settings pattern — intact, still three-place

(1) KNOWN_SETTINGSsrc/loomworks/preferences/person_settings.py. A dict[str, SettingSpec] with three entries today:

| key | default | bounds | kind | |---|---|---|---| | voice.listening.blur_intensity | 0.6 | (0.0, 1.0) | numeric | | voice.listening.silence_submit_seconds | 2.5 | (0.5, 10.0) | numeric | | conversation.message_order | "newest_first" | — (validator) | enum |

SettingSpec shape: (key, display_name, default, validator, bounds?). A numeric setting carries bounds; an enum carries a validator and no bounds. → lists.page_size slots in as a bounded-numeric SettingSpec (e.g. bounds=(10, 200), default=50, an int validator).

(2) _LABEL_TO_KEYsrc/loomworks/orchestration/tune_setting.py. A flat dict[str, str] mapping voice synonyms → setting key (~17 synonyms over the 3 keys: "blur"/"voice blur"→blur; "order"/"message order"→message_order; "wait"/"silence"/"pause"→silence). → add a synonym cluster for the new key (e.g. "page size", "list size", "how many"lists.page_size).

(3) Frontend apply branchsrc/app/operator/engagement/[engagement_address]/useConversation.ts:71–81, the applySettingChange if-ladder (currently a single branch on conversation.message_order), driven by res.setting_changes after each converse turn (useConversation.ts:216). Initial-read + UI-toggle pattern lives in InEngagementSurface.tsx:72–96 (fetchMeSetting/putMeSetting). → a UI-adjustable lists.page_size adds one branch here; a voice-only setting needs no frontend branch (it just re-fetches with the new size).

The three-place extension still describes how a setting is added — confirmed live, unchanged from the Phase-60 pattern.

tune_setting mechanics — confirmed, no drift (orchestration/tune_setting.py):

PUT route: src/loomworks/api/routers/me_settings.py:96@router.put("/me/settings/{setting_key}") (GET at :70), with its own audit write (:159). The direct-set path exists and is live.


D. Load-more surfaces — inventory

The reusable load-more control attaches to live list surfaces. Of the ten CR-1 endpoints, four have an OL consumer today; the home adds two more (Group-A dashboard lenses).

| # | Surface | Component | Endpoint | Paginates today? | |---|---|---|---|---| | 1 | Home — "Your engagements" | engagement-navigation/useHomeData.tslib/api/projects.ts:51 | GET /engagements | No — wholesale | | 2 | Home — "Needs you" lens | useHomeData.tslib/api/dashboard.ts:72 | GET /me/dashboard/needs_you | No — cursor parsed then discarded (engine pages) | | 3 | Home — "Recent" lens | useHomeData.tslib/api/dashboard.ts:119 | GET /me/dashboard/recent | No — cursor discarded (engine pages) | | 4 | Memory room (Held + Settled) | engagement/[…]/MemoryRoom.tsxlib/api/memory.ts:122 | GET …/assertions?state=held|committed&limit=200 | Nolimit=200 ceiling, no cursor (code comment: "pagination — a later increment") | | 5 | Rendering room | engagement/[…]/RenderingRoom.tsxlib/api/renders.ts:105 | GET …/renders?state=produced&limit=200 | Nolimit=200 ceiling, no cursor | | 6 | Conversation transcript (CenterPane) | engagement/[…]/ConversationPane.tsx + useConversation.tslib/api/converse.ts:44 | GET /operator/conversation-history | YES — the Axis 11 "load earlier" precedent; but still sends before= (converse.ts:53) → must migrate to cursor |

The six unconsumed endpointsconsiderations, compositions, shape-events, assertion-groups, manifestations, shapingshave no OL surface today. The Manifestation ("compose") and Shaping ("shape") rooms render empty-state stubs (engagement/[…]/RoomView.tsx:37–47, EmptyRoom), not wired to any engine data. → CR-2 can attach load-more only to surfaces that render a list; these six wait for their rooms to be built (a separate piece). Per-surface page-size defaults attach to surfaces #1–#6, not all ten.

Existing precedent (the reusable control generalises this): CenterPane's "load earlier conversation" button (ConversationPane.tsx:179–188, gated on hasMore) is the working load-more pattern. It's also the one breaking update site: useConversation.ts tracks the oldest created_at and passes it as before (converse.ts:53) — CR-2 must switch it to consume next_cursor and pass cursor, since the engine dropped before.


Net picture for CR-2

  1. One pagination type fits all ten — uniform limit/cursorhas_more/total_count/next_cursor; only the item-array key varies (and compositionsitems, conversation-historyturns).
  2. Home lenses already page on the engine — CR-2 re-wires the frontend to consume next_cursor on /me/dashboard/needs_you + /recent + /engagements; no engine work.
  3. conversation-history is the breaking site — migrate beforecursor in converse.ts/useConversation.ts; the load-earlier control already exists and is the pattern to generalise.
  4. Memory + Rendering rooms swap their limit=200 wholesale fetch for cursor pagination + the load-more control.
  5. Six room endpoints have no surface — out of CR-2's reach until those rooms are built; note as a bounded follow-on, not a silent gap.
  6. lists.page_size adds cleanly via the intact three-place settings pattern (bounded-numeric SettingSpec + _LABEL_TO_KEY synonyms + optional frontend apply-branch), with the dual voice/PUT path live.

HALT. No edits. Report dropped in Downloads; canonical copy staged to loomworks-record/inspection-briefs/ — staged, not committed, awaiting Operator authorisation.


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks list-loading — CR-2 Step-0 inspection report — v0.1 — 2026-06-27 Engine eeae9c2 (alembic 0096) · OL feat-companion-surface 17263c9. Read-only.