Rituals views: one run model, many view types (default per surface, actor-overridable) #231
Labels
No labels
build-dist
priority
P1
priority
P2
priority
P3
priority
P4
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Toilville/toilville-design#231
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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:
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:
channelandinboxare already ratified shapes (packages/udg/docs/SHAPES.md§2). The Session and Inbox artboards are those two shapes.block_specskeyedactor:<id>→ platform surface →any, where user provenance beats forge. The Builder's "Show runs in" writes theanyrow; a user flipping the switcher writes anactor:<id>row. No new storage concept is needed.singlewith the right blocks."confirmrenderer (toilville_rituals#581).board(Kanban) andcalendarexist 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
lineage_entries? Today a run is aconversation_id+turn_numberspan; SHAPES.md §7's data sources arelineageEntries,rituals,patterns,custom— there is noruns. Everything else here depends on this answer.boardandcalendarjoin the canonical shape set?One constraint that may bite:
inbox.rowTemplateforbidsactionsblocks (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
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.
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_intentrather 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 ascustom(raw), so someone can coin a trope today with no code change. And it is almost entirely inert: onlyimportance == criticalrenders anything, and no intent selects a treatment in production.Encouraging sign for the thesis:
CEREMONYalready carries voice synonyms ("approve, deny, confirm, acknowledge, grant"), so one trope already survives a change of modality.The decision: who may coin a
customintent, and what earns promotion fromcustomto 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:
WORKSPACE_PATTERN.md§4: user beats forgeNothing settles consumer vs safety. If a person can restyle and rearrange what they receive, can they quiet a consent ceremony?
sealed_block_idsexists in the schema vocabulary with no implementation, no "Sealed" label, andmin_tiercurrently 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).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.
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_recoveryexists 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.
Status is two axes, not a choice. The states are scheduled, queued, working, waiting and finished.
waitingcarries a kind (permission, approval, review, input) and who it waits for. "Needs you" and "In review" are views ofwaiting. Started, not started and late group the same runs. Late meansscheduled_forhas 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).boardandcalendardo not join the canonical set. They are user-authored patterns overruns(#621). They need two primitives: a group key (state) and a time key (scheduled_for,started_at).Severity picks the container (#182), and form complexity picks the presentation.
Naming. "View type" is the term. "Lens" stays the person's Personality (#189). "Run" is the record. "Session" stays with Forge wish sessions.
Tropes.
semantic_intentstays 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.Authority and the floor.
inbox.rowTemplatestill has noactions.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
textPrimarylabels. 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.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
For Peter (governance only)
runsas a source to SHAPES §7, and move the WORKSPACE_PATTERN §7 starter source from lineage to runs.@peterswimm
Decisions received. Everything the comment recorded as "a separate follow-up" or "tracked as its own task" now has a ticket, since none existed:
bgSurfaceCardvs the canonical DTCGSpec 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 intoschema_snapshot.json, sorunshas 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.swiftmints the conversation id once, hands out 1-based gap-free turn numbers, and hasreleaseTurn(_:)so a lineage write that never lands cannot leave a gap that reads as a lost turn. Wired into the execution path atAppStore.swift:2831.AppController(_conversationId,_currentTurnNumber,_nextFreeTurnNumber, plusturnNumberOverridefor a resumed safety override). It has no equivalent ofreleaseTurn.conversation_id,turn_number,hash_recipe_versionandis_recoveryin 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_idthe key of a durable record.Peter's call on the Inbox rows
Decision 7 upholds
inbox.rowTemplatehaving noactions, 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.
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:
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
runsas 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
Precedence, so this doesn't get re-litigated
Peter's rule, recorded here because it applies to every design review, not just this one:
Applied to the decisions above:
Ville's, and settled:
boardandcalendardo not join the canonical shape set.inbox.rowTemplateclause of 7 — noactionsin a row. Peter's answer to the resulting conflict was that the artboard yields, not that the rule relaxes.Peter's to decide, kept on merit:
CLAUDE.mdputs tier enforcement in code, not in documentation. Tracked as toilville_rituals#633.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
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.
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:
boardandcalendarstay out of the canonical shape set; user-authored patterns overrunsbgSurfaceCardtoken gap)The ordering conditions are no longer prose. They are Forgejo dependencies now, so the graph enforces what this thread could only assert:
#620and#622's successor cannot close before#626and#628— the "filters are decoded but never applied" fix and the parity test.#629cannot close before#578, so the actor-row write path cannot land ahead ofmin_tiermeaning one thing.#630cannot close before#624.#619depends on#620,#621,#623and#629.#622closed 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.
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.