Rituals views: one run model, many view types (default per surface, actor-overridable) #231

Closed
opened 2026-10-26 22:37:46 +00:00 by peterswimm · 8 comments
Owner

Peter got the chat surface working on a ritual block (#580) and proposed a direction past chat, with a wireframe canvas. This is the design half: the decisions that have to land before any of it can be built. The engineering epic is a companion issue in toilville_rituals.

Wireframe: https://claude.ai/artifact/7zU3dexsMJs5mxPuExSbVY?sk=RulB25DZWY5PXB6DFN2EMg — 7 artboards:

# Artboard What it shows
1 Home One "What should we work on?" box; a ritual can be attached before Start. Ritual cards. Active sessions sorted "Needs you" first, with a List / Inbox / Calendar / Kanban switcher
2 Session Left: conversation + collapsed tool calls. Right: the ritual's step checklist, plus Diff / Terminal / Preview / Files tabs. Permission prompts inline
3 Builder A ritual is a vertical list of typed steps (Instruction / Check / Approval gate / Tool). Right rail: trigger, repos, allowed-without-asking, "done when", and "Show runs in" — the per-ritual default view
4 Review Plain-language summary → checks → diff. Inline comments route back to the agent. "Save as ritual" is the loop that grows the library
5 Inbox A "Needs you" queue where a row may be a drafted reply, a permission request, a review-ready run, or an FYI
6 Calendar Scheduled runs + history; Ran / Scheduled / Triggered. Event-driven rituals have no slot
7 Kanban Queued / Working / Needs you / In review / Done, drag to move

The thesis

From the canvas: every ritual produces runs. A run has status, time, steps, owner, outcome. The views are projections over the same runs — nothing is copied. Each ritual sets a default view; a switcher lets anyone flip.

In the chat thread Peter put the same idea as "each channel will have a default view and users can change it for their needs", alongside "the difference between a email and a ticket and a form is only slight".

What this would reuse rather than invent

Worth knowing before deciding, because it narrows the question:

  • channel and inbox are already ratified shapes (packages/udg/docs/SHAPES.md §2). The Session and Inbox artboards are those two shapes.
  • "Default per surface, user can override" is already the resolution order — block_specs keyed actor:<id> → platform surface → any, where user provenance beats forge. The Builder's "Show runs in" writes the any row; a user flipping the switcher writes an actor:<id> row. No new storage concept is needed.
  • A modal is not a shape: SHAPES.md §2 says "Most other patterns (dashboard, error screen, modal) are single with the right blocks."
  • Confirmations are already decided — #182's four severity levels, implemented as a composition rather than a confirm renderer (toilville_rituals#581).

board (Kanban) and calendar exist nowhere. Adding shapes is additive and non-breaking per SHAPES.md §12, so that is a yes/no, not a migration.

Decisions I think are yours

  1. Is a run a first-class entity, or a named span of lineage_entries? Today a run is a conversation_id + turn_number span; SHAPES.md §7's data sources are lineageEntries, rituals, patterns, custom — there is no runs. Everything else here depends on this answer.
  2. Which status taxonomy wins? The thread says started / not-started / late. The canvas says Queued / Working / Needs you / In review / Done. These are different axes — the first is human obligation and carries a lateness idea, the second is agent progress. Nothing in the schema expresses lateness today (no due date, no priority, no assignee).
  3. Do board and calendar join the canonical shape set?
  4. When is a form a modal, and when inline? The canvas says permission prompts are inline, "never as modals". The thread asks for modals for complex forms. The obvious reconciliation is yes/no inline, multi-field modal — but it needs ratifying, and it touches #581's ceremony treatment.
  5. Naming. The canvas calls these "lenses". That word is already taken: in #189 / toilville_rituals#582 a lens is the actor's personal rendering preference (Density, Corners, Motion, Atmosphere), explicitly the same across modes and views. toilville_rituals#496 uses "view type" for what the canvas means. Can we confirm "view type" and retire "lens" in this context?

One constraint that may bite: inbox.rowTemplate forbids actions blocks (SHAPES.md §5, validator rule 3 — "one-tap-per-row is implicit"). The thread's "yes no type boxes" in a row would currently be rejected by the validator. Worth keeping or relaxing deliberately, not by accident.

Open questions carried from the canvas

  • Does the switcher live per-page or per-ritual?
  • Can a user save a custom view (filters + view type)?
  • Which view is the app's landing page — Home, or last used?
  • Other view types later: timeline, table, map of devices?
Peter got the chat surface working on a ritual block (#580) and proposed a direction past chat, with a wireframe canvas. This is the design half: the decisions that have to land before any of it can be built. The engineering epic is a companion issue in `toilville_rituals`. **Wireframe:** https://claude.ai/artifact/7zU3dexsMJs5mxPuExSbVY?sk=RulB25DZWY5PXB6DFN2EMg — 7 artboards: | # | Artboard | What it shows | |---|---|---| | 1 | Home | One "What should we work on?" box; a ritual can be attached before Start. Ritual cards. Active sessions sorted "Needs you" first, with a List / Inbox / Calendar / Kanban switcher | | 2 | Session | Left: conversation + collapsed tool calls. Right: the ritual's step checklist, plus Diff / Terminal / Preview / Files tabs. Permission prompts inline | | 3 | Builder | A ritual is a vertical list of typed steps (Instruction / Check / Approval gate / Tool). Right rail: trigger, repos, allowed-without-asking, "done when", and **"Show runs in"** — the per-ritual default view | | 4 | Review | Plain-language summary → checks → diff. Inline comments route back to the agent. "Save as ritual" is the loop that grows the library | | 5 | Inbox | A "Needs you" queue where a row may be a drafted reply, a permission request, a review-ready run, or an FYI | | 6 | Calendar | Scheduled runs + history; Ran / Scheduled / Triggered. Event-driven rituals have no slot | | 7 | Kanban | Queued / Working / Needs you / In review / Done, drag to move | ## The thesis From the canvas: every ritual produces **runs**. A run has status, time, steps, owner, outcome. The views are projections over the same runs — nothing is copied. Each ritual sets a default view; a switcher lets anyone flip. In the chat thread Peter put the same idea as "each channel will have a default view and users can change it for their needs", alongside "the difference between a email and a ticket and a form is only slight". ## What this would reuse rather than invent Worth knowing before deciding, because it narrows the question: - `channel` and `inbox` are already ratified shapes (`packages/udg/docs/SHAPES.md` §2). The Session and Inbox artboards are those two shapes. - "Default per surface, user can override" is already the resolution order — `block_specs` keyed `actor:<id>` → platform surface → `any`, where user provenance beats forge. The Builder's "Show runs in" writes the `any` row; a user flipping the switcher writes an `actor:<id>` row. No new storage concept is needed. - A modal is not a shape: SHAPES.md §2 says "Most other patterns (dashboard, error screen, modal) are `single` with the right blocks." - Confirmations are already decided — #182's four severity levels, implemented as a composition rather than a `confirm` renderer (toilville_rituals#581). `board` (Kanban) and `calendar` exist nowhere. Adding shapes is additive and non-breaking per SHAPES.md §12, so that is a yes/no, not a migration. ## Decisions I think are yours 1. **Is a *run* a first-class entity, or a named span of `lineage_entries`?** Today a run is a `conversation_id` + `turn_number` span; SHAPES.md §7's data sources are `lineageEntries`, `rituals`, `patterns`, `custom` — there is no `runs`. Everything else here depends on this answer. 2. **Which status taxonomy wins?** The thread says *started / not-started / late*. The canvas says *Queued / Working / Needs you / In review / Done*. These are different axes — the first is human obligation and carries a lateness idea, the second is agent progress. Nothing in the schema expresses lateness today (no due date, no priority, no assignee). 3. **Do `board` and `calendar` join the canonical shape set?** 4. **When is a form a modal, and when inline?** The canvas says permission prompts are inline, "never as modals". The thread asks for modals for complex forms. The obvious reconciliation is yes/no inline, multi-field modal — but it needs ratifying, and it touches #581's ceremony treatment. 5. **Naming.** The canvas calls these "lenses". That word is already taken: in #189 / toilville_rituals#582 a *lens* is the actor's personal rendering preference (Density, Corners, Motion, Atmosphere), explicitly the same across modes and views. toilville_rituals#496 uses **"view type"** for what the canvas means. Can we confirm "view type" and retire "lens" in this context? One constraint that may bite: `inbox.rowTemplate` forbids `actions` blocks (SHAPES.md §5, validator rule 3 — "one-tap-per-row is implicit"). The thread's "yes no type boxes" in a row would currently be rejected by the validator. Worth keeping or relaxing deliberately, not by accident. ## Open questions carried from the canvas - Does the switcher live per-page or per-ritual? - Can a user save a custom view (filters + view type)? - Which view is the app's landing page — Home, or last used? - Other view types later: timeline, table, map of devices?
Author
Owner

Engineering epic for this is Toilville/toilville_rituals#619, with children #620 (runs as a data source), #621 (board + calendar shapes), #622 (default view per surface), #623 (task state). All of them are blocked on the decisions above — #620 and #623 especially, since they can't be specified until questions 1 and 2 are answered.

The sidecar handoff from the same thread ("get Word", "launch into Affinity") is filed separately as Toilville/toilville_rituals#624, outside the epic: it's a consent question before it's a design one. #205 may be the nearer relative on the Affinity half.

Engineering epic for this is Toilville/toilville_rituals#619, with children #620 (runs as a data source), #621 (board + calendar shapes), #622 (default view per surface), #623 (task state). All of them are blocked on the decisions above — #620 and #623 especially, since they can't be specified until questions 1 and 2 are answered. The sidecar handoff from the same thread ("get Word", "launch into Affinity") is filed separately as Toilville/toilville_rituals#624, outside the epic: it's a consent question before it's a design one. #205 may be the nearer relative on the Affinity half.
Author
Owner

Three more decisions, after a research pass through the client. These matter more than the original five, because they turned out to be about authorship rather than layout — and the architecture already takes positions on them that nothing enforces.

6. The trope vocabulary

Peter's framing is that people should hold a repository of common tropes. A trope is a social form — an approval, a nudge, a digest, a handoff — and it stays itself whether it renders as a row, a card, a modal or a voice prompt. That is semantic, not visual, so it belongs with semantic_intent rather than in SHAPES.md.

That layer already exists and is already cross-port: 11 intents (ACTION_PRIMARY, CONTENT_HEADING, COLLECTION_LIST, CEREMONY, CONTAINER, …) plus four importance levels, identical in Dart and Swift. It is also open — an unknown value round-trips as custom(raw), so someone can coin a trope today with no code change. And it is almost entirely inert: only importance == critical renders anything, and no intent selects a treatment in production.

Encouraging sign for the thesis: CEREMONY already carries voice synonyms ("approve, deny, confirm, acknowledge, grant"), so one trope already survives a change of modality.

The decision: who may coin a custom intent, and what earns promotion from custom to a known value that both ports render deliberately? Tracked for engineering as Toilville/toilville_rituals#627.

7. Three-party precedence

Two of the three pairings are settled and the third is not:

  • author vs consumer — WORKSPACE_PATTERN.md §4: user beats forge
  • mode vs lens — #189 / toilville_rituals#582: a lens may lower Atmosphere, never raise it, never touch Accent

Nothing settles consumer vs safety. If a person can restyle and rearrange what they receive, can they quiet a consent ceremony? sealed_block_ids exists in the schema vocabulary with no implementation, no "Sealed" label, and min_tier currently means three different things across the two ports and the spec (toilville_rituals#578).

Worth noting the precedent that already works: OS reduced motion and high contrast already override the author, implemented and tested. The author losing to the viewer is established — the question is only where it stops.

The decision: what is the irreducible floor a consumer may not restyle away?

8. Patterns and lineage

Rituals never deletes a lineage entry. But editing a pattern you pulled from the Forge keeps the original id and gets silently replaced on the next sync — your authorship is discarded without a trace. There is no fork or derived-from relation at all.

The decision: do patterns get the same never-destroyed, always-traceable treatment runs already have, such that a fork is itself a lineage event? Engineering is Toilville/toilville_rituals#630.


One thing the research reframed, which may interest you: WORKSPACE_PATTERN.md §7 is explicit that the starter workspace is deliberately the shape every inbox app and Slack-alike shares. So the familiar shape is an intentional on-ramp, not an oversight. The risk isn't that it's familiar — it's that nothing was ever built to get off it. That's why Kanban and Calendar were pulled out of the canonical shape set and re-aimed as the first user-authored published patterns (toilville_rituals#621).

Three more decisions, after a research pass through the client. These matter more than the original five, because they turned out to be about authorship rather than layout — and the architecture already takes positions on them that nothing enforces. ## 6. The trope vocabulary Peter's framing is that people should hold a repository of common tropes. A trope is a social form — an approval, a nudge, a digest, a handoff — and it stays itself whether it renders as a row, a card, a modal or a voice prompt. That is semantic, not visual, so it belongs with `semantic_intent` rather than in SHAPES.md. That layer already exists and is already cross-port: 11 intents (`ACTION_PRIMARY`, `CONTENT_HEADING`, `COLLECTION_LIST`, `CEREMONY`, `CONTAINER`, …) plus four importance levels, identical in Dart and Swift. It is also **open** — an unknown value round-trips as `custom(raw)`, so someone can coin a trope today with no code change. And it is almost entirely inert: only `importance == critical` renders anything, and no intent selects a treatment in production. Encouraging sign for the thesis: `CEREMONY` already carries voice synonyms ("approve, deny, confirm, acknowledge, grant"), so one trope already survives a change of modality. **The decision:** who may coin a `custom` intent, and what earns promotion from `custom` to a known value that both ports render deliberately? Tracked for engineering as Toilville/toilville_rituals#627. ## 7. Three-party precedence Two of the three pairings are settled and the third is not: - author vs consumer — `WORKSPACE_PATTERN.md` §4: **user beats forge** - mode vs lens — #189 / toilville_rituals#582: a lens may lower Atmosphere, never raise it, never touch Accent Nothing settles **consumer vs safety**. If a person can restyle and rearrange what they receive, can they quiet a consent ceremony? `sealed_block_ids` exists in the schema vocabulary with no implementation, no "Sealed" label, and `min_tier` currently means three different things across the two ports and the spec (toilville_rituals#578). Worth noting the precedent that already works: OS reduced motion and high contrast already override the author, implemented and tested. The author losing to the viewer is established — the question is only where it stops. **The decision:** what is the irreducible floor a consumer may not restyle away? ## 8. Patterns and lineage Rituals never deletes a lineage entry. But editing a pattern you pulled from the Forge keeps the original id and gets silently replaced on the next sync — your authorship is discarded without a trace. There is no fork or derived-from relation at all. **The decision:** do patterns get the same never-destroyed, always-traceable treatment runs already have, such that a fork is itself a lineage event? Engineering is Toilville/toilville_rituals#630. --- One thing the research reframed, which may interest you: `WORKSPACE_PATTERN.md` §7 is explicit that the starter workspace is **deliberately** the shape every inbox app and Slack-alike shares. So the familiar shape is an intentional on-ramp, not an oversight. The risk isn't that it's familiar — it's that nothing was ever built to get off it. That's why Kanban and Calendar were pulled out of the canonical shape set and re-aimed as the first user-authored published patterns (toilville_rituals#621).
Member

Decisions for #231: run model, views, authority

Numbers follow the issue. These are Ville's decisions, made under the design and vocabulary delegation. Section "For Peter" lists the only items that need Peter.

  1. A run is a first-class record, created at Queue or Start. The record holds current state only. Lineage stays the immutable proof. The key is conversation_id, which hash recipe v2 already covers. Runs are archived, never deleted. Crash recovery appends a recovery lineage row (is_recovery exists for this) and never edits one.

    Where a run lives. A queued, scheduled or waiting run cannot be reconstructed today. Only finished turns reach lineage. So the run record must be durable. It is not a cache. This is the v1 boundary. It is not a permanent architecture.

    • Run records are persistent and device-local.
    • Tier 3 may sync lineage. Mutable run state does not sync in v1.
    • A scheduled run belongs to one device. Two devices never fire the same schedule.
    • Future run-state egress or sync needs a separate consent and governance review.
    • macOS conversation-envelope parity is a separate implementation prerequisite. It is tracked as its own task.
  2. Status is two axes, not a choice. The states are scheduled, queued, working, waiting and finished. waiting carries a kind (permission, approval, review, input) and who it waits for. "Needs you" and "In review" are views of waiting. Started, not started and late group the same runs. Late means scheduled_for has passed with no start. Lifecycle and human attention are separate. A run may be finished or failed and still need a person. It then projects into Needs you until the person acknowledges or resolves it. There is no "failed-unseen" lifecycle state. So #623 needs no second task model. Wish obligation stays Forge-owned (#181).

  3. board and calendar do not join the canonical set. They are user-authored patterns over runs (#621). They need two primitives: a group key (state) and a time key (scheduled_for, started_at).

  4. Severity picks the container (#182), and form complexity picks the presentation.

    • A simple interaction is inline.
    • A contextual or ordinary form renders in the pane where that fits.
    • A focused, complex form may use a modal.
    • Sufficiently complex or external work may hand off to a sidecar.
    • Review, Ceremony, Destructive and safety-override confirmation keeps its own dialog treatment.
    • The #182 confirmation dialog never nests over a form dialog. The form presentation ends first. Then the confirmation step starts.
    • "Always allow in this ritual" is a standing grant, so Review at least.
  5. Naming. "View type" is the term. "Lens" stays the person's Personality (#189). "Run" is the record. "Session" stays with Forge wish sessions.

  6. Tropes. semantic_intent stays open. Anyone may coin a custom intent locally. Publishing one uses the normal publish right (#630). Promotion needs a published pattern that uses it, deliberate rendering on both ports under a parity test, validator support and docs. A custom intent never carries sealing or safety meaning.

  7. Authority and the floor.

    • Unattended start needs an explicit, revocable standing grant. A schedule or trigger says when a run starts. The grant says whether it may start unattended and with which capabilities. Creating or widening it uses the #182 Ceremony.
    • At fire time the run fails closed. A missing or invalid tier, start grant or capability grant executes nothing and moves the run to Needs you.
    • Authority does not travel with a pulled ritual. Schedules, triggers and "allowed without asking" are requests. The consumer grants their own.
    • Sealed items render regardless of any override, checked at render time. No view type, row, shortcut or drag weakens a requirement (#182). They run the same commands at the same severity. inbox.rowTemplate still has no actions.
  8. Patterns get lineage. They are never destroyed, and a fork is a lineage event. The mechanism is in #630: hash-chained, append-only, local at Tier 0, no ritual FK. Publish severity is decided with #624, because both move data out of custody.

Views. Choosing a view type is a pointer preference, not a copy. Customisation is an actor: row per view type. Reset view deletes the person's overrides and returns the ladder default.

Home stays the inbox-shape starter, now over runs. Needs you comes first. The ask and start control sits in the header. The switcher changes the view type of the whole collection: Inbox, List, Calendar, Kanban, later user-authored patterns. Rituals is its own destination. SHAPES §13 is unchanged.

Visual direction. The attached overview shows Inbox, Kanban, Calendar and Run detail on one run model. It follows the existing rules. Fills replace outlines. Only text inputs get an outline. Lines appear only where they carry meaning: the 3px mode-colour selection edge, the 4px sidebar stripe and the 2px focus ring. Cards use textPrimary labels. Mode colour marks the Cast button and the selection. "Cast" is the submit verb. A dragged card keeps its normal card look. The drop target shows where it can go.

  • Board card titles are 16px. This is accepted as the design direction for compact board slots. #231 does not change the ratified global title rule. The need for a compact-slot typography rule is recorded as a separate follow-up.
  • Card surface. The wireframes use the card fill that the Flutter and macOS registries call bgSurfaceCard. The canonical DTCG has no such token. This Flutter and macOS card-surface mismatch is recorded as a separate token and design-system gap. #231 does not propose a canonical token. That needs its own evidence and decision.

Conditions

  • Order of work: #626 and #628 first, because filters are decoded but never applied. #629 and #578 land before any override write path.
  • Until Peter approves the unattended-start policy, runtime behaviour equals human start. Event triggers and a cloud runner stay Tier 2+.

For Peter (governance only)

  1. Unattended-start consent policy. Approve the new policy, a ritual × capability grant scope, a duration rule and the executor. Until then no unattended run is enabled.
  2. Run-state egress. v1 sends no run state beyond lineage. Any later run-state egress or sync is new data egress. It requires a separate consent and governance review before it is enabled.
  3. Spec amendment. Add runs as a source to SHAPES §7, and move the WORKSPACE_PATTERN §7 starter source from lineage to runs.

@peterswimm

**Decisions for #231: run model, views, authority** Numbers follow the issue. These are Ville's decisions, made under the design and vocabulary delegation. Section "For Peter" lists the only items that need Peter. 1. **A run is a first-class record, created at Queue or Start.** The record holds current state only. Lineage stays the immutable proof. The key is `conversation_id`, which hash recipe v2 already covers. Runs are archived, never deleted. Crash recovery appends a recovery lineage row (`is_recovery` exists for this) and never edits one. **Where a run lives.** A queued, scheduled or waiting run cannot be reconstructed today. Only finished turns reach lineage. So the run record must be durable. It is not a cache. This is the v1 boundary. It is not a permanent architecture. - **Run records are persistent and device-local.** - **Tier 3 may sync lineage. Mutable run state does not sync in v1.** - **A scheduled run belongs to one device.** Two devices never fire the same schedule. - **Future run-state egress or sync needs a separate consent and governance review.** - **macOS conversation-envelope parity** is a separate implementation prerequisite. It is tracked as its own task. 2. **Status is two axes, not a choice.** The states are scheduled, queued, working, waiting and finished. `waiting` carries a kind (permission, approval, review, input) and who it waits for. "Needs you" and "In review" are views of `waiting`. Started, not started and late group the same runs. Late means `scheduled_for` has passed with no start. Lifecycle and human attention are separate. A run may be finished or failed and still need a person. It then projects into Needs you until the person acknowledges or resolves it. There is no "failed-unseen" lifecycle state. So #623 needs no second task model. Wish obligation stays Forge-owned (#181). 3. **`board` and `calendar` do not join the canonical set.** They are user-authored patterns over `runs` (#621). They need two primitives: a group key (`state`) and a time key (`scheduled_for`, `started_at`). 4. **Severity picks the container (#182), and form complexity picks the presentation.** - A simple interaction is inline. - A contextual or ordinary form renders in the pane where that fits. - A focused, complex form may use a modal. - Sufficiently complex or external work may hand off to a sidecar. - Review, Ceremony, Destructive and safety-override confirmation keeps its own dialog treatment. - The #182 confirmation dialog never nests over a form dialog. The form presentation ends first. Then the confirmation step starts. - "Always allow in this ritual" is a standing grant, so Review at least. 5. **Naming.** "View type" is the term. "Lens" stays the person's Personality (#189). "Run" is the record. "Session" stays with Forge wish sessions. 6. **Tropes.** `semantic_intent` stays open. Anyone may coin a custom intent locally. Publishing one uses the normal publish right (#630). Promotion needs a published pattern that uses it, deliberate rendering on both ports under a parity test, validator support and docs. A custom intent never carries sealing or safety meaning. 7. **Authority and the floor.** - Unattended start needs an explicit, revocable standing grant. A schedule or trigger says when a run starts. The grant says whether it may start unattended and with which capabilities. Creating or widening it uses the #182 Ceremony. - At fire time the run fails closed. A missing or invalid tier, start grant or capability grant executes nothing and moves the run to Needs you. - Authority does not travel with a pulled ritual. Schedules, triggers and "allowed without asking" are requests. The consumer grants their own. - Sealed items render regardless of any override, checked at render time. No view type, row, shortcut or drag weakens a requirement (#182). They run the same commands at the same severity. `inbox.rowTemplate` still has no `actions`. 8. **Patterns get lineage.** They are never destroyed, and a fork is a lineage event. The mechanism is in #630: hash-chained, append-only, local at Tier 0, no ritual FK. Publish severity is decided with #624, because both move data out of custody. **Views.** Choosing a view type is a pointer preference, not a copy. Customisation is an `actor:` row per view type. Reset view deletes the person's overrides and returns the ladder default. **Home** stays the inbox-shape starter, now over `runs`. Needs you comes first. The ask and start control sits in the header. The switcher changes the view type of the whole collection: Inbox, List, Calendar, Kanban, later user-authored patterns. Rituals is its own destination. SHAPES §13 is unchanged. **Visual direction.** The attached overview shows Inbox, Kanban, Calendar and Run detail on one run model. It follows the existing rules. Fills replace outlines. Only text inputs get an outline. Lines appear only where they carry meaning: the 3px mode-colour selection edge, the 4px sidebar stripe and the 2px focus ring. Cards use `textPrimary` labels. Mode colour marks the Cast button and the selection. "Cast" is the submit verb. A dragged card keeps its normal card look. The drop target shows where it can go. - **Board card titles are 16px.** This is accepted as the design direction for compact board slots. #231 does not change the ratified global title rule. The need for a compact-slot typography rule is recorded as a separate follow-up. - **Card surface.** The wireframes use the card fill that the Flutter and macOS registries call `bgSurfaceCard`. The canonical DTCG has no such token. This Flutter and macOS card-surface mismatch is recorded as a separate token and design-system gap. #231 does not propose a canonical token. That needs its own evidence and decision. **Conditions** - Order of work: #626 and #628 first, because filters are decoded but never applied. #629 and #578 land before any override write path. - Until Peter approves the unattended-start policy, runtime behaviour equals human start. Event triggers and a cloud runner stay Tier 2+. **For Peter** (governance only) 1. **Unattended-start consent policy.** Approve the new policy, a ritual × capability grant scope, a duration rule and the executor. Until then no unattended run is enabled. 2. **Run-state egress.** v1 sends no run state beyond lineage. Any later run-state egress or sync is new data egress. It requires a separate consent and governance review before it is enabled. 3. **Spec amendment.** Add `runs` as a source to SHAPES §7, and move the WORKSPACE_PATTERN §7 starter source from lineage to runs. @peterswimm
Author
Owner

Decisions received. Everything the comment recorded as "a separate follow-up" or "tracked as its own task" now has a ticket, since none existed:

From the decision Ticket
Compact-slot typography rule (16px board card titles) Toilville/toilville-design#232
bgSurfaceCard vs the canonical DTCG Toilville/toilville-design#233
Unattended-start consent policy (For Peter 1) Toilville/toilville_rituals#633
Run-state egress review (For Peter 2) Toilville/toilville_rituals#634
Conversation envelope, re-scoped (see below) Toilville/toilville_rituals#635

Spec amendment (For Peter 3) is folded into toilville_rituals#620 rather than filed separately — it already carried the SHAPES §7 half, and the WORKSPACE_PATTERN §7 starter-source move is now recorded there too. That half is bigger than it reads: the starter contract is a checked-in artifact (assets/block_specs/nav.inbox.blocks.json) embedded into schema_snapshot.json, so runs has to decode as a source before the artifact can name it.

Conversation envelope — engineering scope, no design input needed

The decision lists macOS conversation-envelope parity as an implementation prerequisite. This one sits on the engineering side of the line, so it is off the design review's plate entirely — recording the measurement here only so the dependency in the ordering above can be dropped.

Measured on main:

  • apps/rituals-macos/RitualsApp/ConversationTracker.swift mints the conversation id once, hands out 1-based gap-free turn numbers, and has releaseTurn(_:) so a lineage write that never lands cannot leave a gap that reads as a lost turn. Wired into the execution path at AppStore.swift:2831.
  • Flutter has the same capability inline on AppController (_conversationId, _currentTurnNumber, _nextFreeTurnNumber, plus turnNumberOverride for a resumed safety override). It has no equivalent of releaseTurn.
  • Both schemas carry conversation_id, turn_number, hash_recipe_version and is_recovery in create and upgrade paths, with both conversation indexes — Dart v30, Swift v18. Hash recipe v2 keys off conversation-id presence on both.

So nothing blocks on building the envelope; it exists on both ports. The real gap is narrower and is now #635: the ports derive turn numbers from different sources (macOS an in-memory counter, Flutter the ledger maximum) and mint different id formats, with no test holding them to one answer. That matters more under decision 1, which makes conversation_id the key of a durable record.

Peter's call on the Inbox rows

Decision 7 upholds inbox.rowTemplate having no actions, which conflicts with the Inbox artboard's actionable rows and with the original "yes no type boxes" ask. Peter's answer: the artboard yields — it can just be overridden. The rule stands as ruled; the wireframe is the flexible half.

The governance items in #633 and #634 are open and unanswered — filing them is tracking, not approval. Per the condition in the decision, runtime behaviour equals human start until #633 is approved.

Decisions received. Everything the comment recorded as "a separate follow-up" or "tracked as its own task" now has a ticket, since none existed: | From the decision | Ticket | |---|---| | Compact-slot typography rule (16px board card titles) | Toilville/toilville-design#232 | | `bgSurfaceCard` vs the canonical DTCG | Toilville/toilville-design#233 | | Unattended-start consent policy (For Peter 1) | Toilville/toilville_rituals#633 | | Run-state egress review (For Peter 2) | Toilville/toilville_rituals#634 | | Conversation envelope, re-scoped (see below) | Toilville/toilville_rituals#635 | Spec amendment (For Peter 3) is folded into toilville_rituals#620 rather than filed separately — it already carried the SHAPES §7 half, and the WORKSPACE_PATTERN §7 starter-source move is now recorded there too. That half is bigger than it reads: the starter contract is a checked-in artifact (`assets/block_specs/nav.inbox.blocks.json`) embedded into `schema_snapshot.json`, so `runs` has to decode as a source before the artifact can name it. ## Conversation envelope — engineering scope, no design input needed The decision lists macOS conversation-envelope parity as an implementation prerequisite. This one sits on the engineering side of the line, so it is off the design review's plate entirely — recording the measurement here only so the dependency in the ordering above can be dropped. Measured on `main`: - `apps/rituals-macos/RitualsApp/ConversationTracker.swift` mints the conversation id once, hands out 1-based gap-free turn numbers, and has `releaseTurn(_:)` so a lineage write that never lands cannot leave a gap that reads as a lost turn. Wired into the execution path at `AppStore.swift:2831`. - Flutter has the same capability inline on `AppController` (`_conversationId`, `_currentTurnNumber`, `_nextFreeTurnNumber`, plus `turnNumberOverride` for a resumed safety override). It has no equivalent of `releaseTurn`. - Both schemas carry `conversation_id`, `turn_number`, `hash_recipe_version` and `is_recovery` in create *and* upgrade paths, with both conversation indexes — Dart v30, Swift v18. Hash recipe v2 keys off conversation-id presence on both. So nothing blocks on building the envelope; it exists on both ports. The real gap is narrower and is now #635: the ports derive turn numbers from different sources (macOS an in-memory counter, Flutter the ledger maximum) and mint different id formats, with no test holding them to one answer. That matters more under decision 1, which makes `conversation_id` the key of a durable record. ## Peter's call on the Inbox rows Decision 7 upholds `inbox.rowTemplate` having no `actions`, which conflicts with the Inbox artboard's actionable rows and with the original "yes no type boxes" ask. Peter's answer: **the artboard yields — it can just be overridden.** The rule stands as ruled; the wireframe is the flexible half. The governance items in #633 and #634 are open and unanswered — filing them is tracking, not approval. Per the condition in the decision, runtime behaviour equals human start until #633 is approved.
Author
Owner

Governance decisions on the three items in #issuecomment-37801. The rest stand as made under the delegation.

Unattended start exists. A grant is:

  • Scoped to one ritual × one capability. No blanket "this ritual may act unattended", and no "this capability is unattended everywhere".
  • Expiring. Every grant carries a duration and dies at the end of it. There is no permanent unattended grant, and renewal is a new grant rather than an extension.
  • Device-local executor only. The executor is the device that owns the schedule.
  • Created or widened through the #182 Ceremony, as you specified.
  • Revocable at any time, taking effect at the next fire.

Fail-closed as written in your decision 7: a missing, expired or invalid grant executes nothing and moves the run to Needs you. Expiry is the ordinary case rather than an error, so it should read as "this needs you again", not as a failure — which is what the overview already draws ("Permission to start on its own: ran out Sunday", with Renew).

A cloud runner is a separate approval. This one does not cover it.

2. Run-state egress — confirmed, and wider than asked

v1 sends no run state beyond lineage. Confirmed.

Tightening it: the hold covers Tier 3 lineage sync as well, not only mutable run state. Decision 1 says Tier 3 may sync lineage — may, not does. In v1 nothing about runs leaves the device by either route. Enabling either one is new data egress and takes its own consent and governance review.

3. Spec amendment — approved

Add runs as a source to SHAPES §7, and move the WORKSPACE_PATTERN §7 starter source from lineage to runs.


Conditions stand as you set them: #626 and #628 first, #629 and #578 before any override write path.

@ville

**Governance decisions on the three items in #issuecomment-37801.** The rest stand as made under the delegation. ## 1. Unattended-start consent policy — approved, narrowly scoped Unattended start exists. A grant is: - **Scoped to one ritual × one capability.** No blanket "this ritual may act unattended", and no "this capability is unattended everywhere". - **Expiring.** Every grant carries a duration and dies at the end of it. There is no permanent unattended grant, and renewal is a new grant rather than an extension. - **Device-local executor only.** The executor is the device that owns the schedule. - **Created or widened through the #182 Ceremony**, as you specified. - **Revocable at any time**, taking effect at the next fire. Fail-closed as written in your decision 7: a missing, expired or invalid grant executes nothing and moves the run to Needs you. Expiry is the ordinary case rather than an error, so it should read as "this needs you again", not as a failure — which is what the overview already draws ("Permission to start on its own: ran out Sunday", with Renew). A cloud runner is a separate approval. This one does not cover it. ## 2. Run-state egress — confirmed, and wider than asked v1 sends no run state beyond lineage. Confirmed. Tightening it: the hold covers **Tier 3 lineage sync as well**, not only mutable run state. Decision 1 says Tier 3 *may* sync lineage — may, not does. In v1 nothing about runs leaves the device by either route. Enabling either one is new data egress and takes its own consent and governance review. ## 3. Spec amendment — approved Add `runs` as a source to SHAPES §7, and move the WORKSPACE_PATTERN §7 starter source from lineage to runs. --- Conditions stand as you set them: #626 and #628 first, #629 and #578 before any override write path. @ville
Author
Owner

Precedence, so this doesn't get re-litigated

Peter's rule, recorded here because it applies to every design review, not just this one:

If it's a design choice, ville wins. If it makes the technical design worse, Peter wins.

Applied to the decisions above:

Ville's, and settled:

  • 2 — the two-axis status model. Asked as Q2; it is the vocabulary people see, and splitting lifecycle from attention is cleaner than either option that was offered.
  • 3 — board and calendar do not join the canonical shape set.
  • 4 — severity picks the container, complexity picks the presentation. (One constraint on it below.)
  • 5 — view type / lens / run / session.
  • 6 — the trope vocabulary and its promotion bar. "A custom intent never carries sealing or safety meaning" tightens the floor, so it stands.
  • The inbox.rowTemplate clause of 7 — no actions in a row. Peter's answer to the resulting conflict was that the artboard yields, not that the rule relaxes.
  • All of the visual direction — fills over outlines, the 3px/4px/2px line rules, Cast as the submit verb, card and drag treatment.

Peter's to decide, kept on merit:

  • 1 — run as a first-class durable record, device-local, no run-state sync in v1. Data architecture.
  • 7 — standing grants, #182 Ceremony to create or widen, fail-closed at fire time, authority not travelling with a pulled ritual. Consent architecture: CLAUDE.md puts tier enforcement in code, not in documentation. Tracked as toilville_rituals#633.
  • 8 — patterns get lineage, hash-chained and append-only, no ritual FK. Storage architecture. Tracked in toilville_rituals#630 and #634.
  • The order of work conditions.

To be clear about what this changes: nothing in Peter's column is being reversed. Decision 1's split of mutable run state from immutable lineage proof is correct and matches the project's lineage-integrity rule. 7 and 8 are conservative in the right direction. The distinction is between "kept because it is right" and "settled because the design review said so" — only the second is being withdrawn, and only so it is not quoted later as decided.

One constraint on decision 4

The #182 confirmation dialog never nests over a form dialog. The form presentation ends first. Then the confirmation step starts.

Never-nest is right. The sequencing is the problem: "the form presentation ends first" tears down a filled-in form before the person confirms, and nothing here says what happens to their input if they then cancel. That is a data-loss hazard in the shape of a layering rule.

The design intent survives either fix — the form stays alive and inert behind the confirmation, or its state is explicitly preserved and restored on cancel. Which one is ville's call. Filed as Toilville/toilville-design#235 so it isn't buried in a thread.

## Precedence, so this doesn't get re-litigated Peter's rule, recorded here because it applies to every design review, not just this one: > **If it's a design choice, ville wins. If it makes the technical design worse, Peter wins.** Applied to the decisions above: **Ville's, and settled:** - **2** — the two-axis status model. Asked as Q2; it is the vocabulary people see, and splitting lifecycle from attention is cleaner than either option that was offered. - **3** — `board` and `calendar` do not join the canonical shape set. - **4** — severity picks the container, complexity picks the presentation. (One constraint on it below.) - **5** — view type / lens / run / session. - **6** — the trope vocabulary and its promotion bar. "A custom intent never carries sealing or safety meaning" tightens the floor, so it stands. - The `inbox.rowTemplate` clause of **7** — no `actions` in a row. Peter's answer to the resulting conflict was that the artboard yields, not that the rule relaxes. - All of the **visual direction** — fills over outlines, the 3px/4px/2px line rules, Cast as the submit verb, card and drag treatment. **Peter's to decide, kept on merit:** - **1** — run as a first-class durable record, device-local, no run-state sync in v1. Data architecture. - **7** — standing grants, #182 Ceremony to create or widen, fail-closed at fire time, authority not travelling with a pulled ritual. Consent architecture: `CLAUDE.md` puts tier enforcement in code, not in documentation. Tracked as toilville_rituals#633. - **8** — patterns get lineage, hash-chained and append-only, no ritual FK. Storage architecture. Tracked in toilville_rituals#630 and #634. - The **order of work** conditions. To be clear about what this changes: **nothing in Peter's column is being reversed.** Decision 1's split of mutable run state from immutable lineage proof is correct and matches the project's lineage-integrity rule. 7 and 8 are conservative in the right direction. The distinction is between "kept because it is right" and "settled because the design review said so" — only the second is being withdrawn, and only so it is not quoted later as decided. ## One constraint on decision 4 > The #182 confirmation dialog never nests over a form dialog. The form presentation ends first. Then the confirmation step starts. Never-nest is right. The sequencing is the problem: "the form presentation ends first" tears down a filled-in form *before* the person confirms, and nothing here says what happens to their input if they then cancel. That is a data-loss hazard in the shape of a layering rule. The design intent survives either fix — the form stays alive and inert behind the confirmation, or its state is explicitly preserved and restored on cancel. Which one is ville's call. Filed as Toilville/toilville-design#235 so it isn't buried in a thread.
Author
Owner

Closing. Every decision is made and every piece of follow-on work has its own ticket — there is nothing left in this issue but the record.

Map of where each one went, so nobody has to re-read the thread to find out:

# Decision Lives in
1 Run is a first-class durable record, device-local Toilville/toilville_rituals#620 (runs as a data source), #635 (conversation envelope, premise corrected)
2 Status is two axes — lifecycle and attention #623
3 board and calendar stay out of the canonical shape set; user-authored patterns over runs #621
4 Severity picks the container, complexity picks the presentation #235 (the never-nest sequencing hazard), #581
5 Naming — view type / lens / run / session Settled here. No ticket needed.
6 Trope vocabulary and the promotion bar #627
7 Authority, standing grants, fail-closed, sealed items #633 (unattended start, approved), #629 + #578 (the override clamp)
8 Patterns get lineage #630, with publish severity decided alongside #624
— Visual direction #232 (compact-slot typography), #233 (bgSurfaceCard token gap)
— Spec amendment (For Peter 3) Folded into #620, both halves
— Run-state egress (For Peter 2) #634 — confirmed and tightened: the hold covers Tier 3 lineage sync too, not only mutable run state

The ordering conditions are no longer prose. They are Forgejo dependencies now, so the graph enforces what this thread could only assert:

  • #620 and #622's successor cannot close before #626 and #628 — the "filters are decoded but never applied" fix and the parity test.
  • #629 cannot close before #578, so the actor-row write path cannot land ahead of min_tier meaning one thing.
  • #630 cannot close before #624.
  • The epic #619 depends on #620, #621, #623 and #629.

#622 closed as superseded by #629, per the 2026-10-26 reasoning — keeping them apart is what would have allowed the write path to ship without its clamp.

Precedence, as recorded above: if it is a design choice, ville wins; if it makes the technical design worse, Peter wins. Nothing in either column was reversed in this thread.

Reopen if something here turns out to be underspecified rather than merely unbuilt. Otherwise the work continues in the tickets.

**Closing. Every decision is made and every piece of follow-on work has its own ticket — there is nothing left in this issue but the record.** Map of where each one went, so nobody has to re-read the thread to find out: | # | Decision | Lives in | |---|---|---| | 1 | Run is a first-class durable record, device-local | Toilville/toilville_rituals#620 (runs as a data source), #635 (conversation envelope, premise corrected) | | 2 | Status is two axes — lifecycle and attention | #623 | | 3 | `board` and `calendar` stay out of the canonical shape set; user-authored patterns over `runs` | #621 | | 4 | Severity picks the container, complexity picks the presentation | #235 (the never-nest sequencing hazard), #581 | | 5 | Naming — view type / lens / run / session | Settled here. No ticket needed. | | 6 | Trope vocabulary and the promotion bar | #627 | | 7 | Authority, standing grants, fail-closed, sealed items | #633 (unattended start, approved), #629 + #578 (the override clamp) | | 8 | Patterns get lineage | #630, with publish severity decided alongside #624 | | — | Visual direction | #232 (compact-slot typography), #233 (`bgSurfaceCard` token gap) | | — | Spec amendment (For Peter 3) | Folded into #620, both halves | | — | Run-state egress (For Peter 2) | #634 — **confirmed and tightened**: the hold covers Tier 3 lineage sync too, not only mutable run state | **The ordering conditions are no longer prose.** They are Forgejo dependencies now, so the graph enforces what this thread could only assert: - `#620` and `#622`'s successor cannot close before `#626` and `#628` — the "filters are decoded but never applied" fix and the parity test. - `#629` cannot close before `#578`, so the actor-row write path cannot land ahead of `min_tier` meaning one thing. - `#630` cannot close before `#624`. - The epic `#619` depends on `#620`, `#621`, `#623` and `#629`. `#622` closed as superseded by `#629`, per the 2026-10-26 reasoning — keeping them apart is what would have allowed the write path to ship without its clamp. **Precedence, as recorded above:** if it is a design choice, ville wins; if it makes the technical design worse, Peter wins. Nothing in either column was reversed in this thread. Reopen if something here turns out to be underspecified rather than merely unbuilt. Otherwise the work continues in the tickets.
Member

Decision 4 is amended. The last bullet ("never nests ... The form presentation ends first. Then the confirmation step starts.") is superseded by Toilville/toilville-design#235: the confirmation is the only interactive surface, not the only visible surface. The form stays mounted, visible and inert behind it, and is destroyed only after the action succeeds.

Decision 4 is amended. The last bullet ("never nests ... The form presentation ends first. Then the confirmation step starts.") is superseded by Toilville/toilville-design#235: the confirmation is the only interactive surface, not the only visible surface. The form stays mounted, visible and inert behind it, and is destroyed only after the action succeeds.
Sign in to join this conversation.
No description provided.