--- created: '2026-07-18' path: Sources/Dev project: dispatch-message-channel-sorting tags: - dispatch - petal-dispatch - lovebug - ui - rendering - markdown-it - fence-dispatch - python type: project-plan --- ## Problem petal-dispatch renders every Lovebug turn as uniform monospace prose — a "wall." The surrounding chrome (status bar, node pills, code fences) already feels modern; the message body does not. The wall is not a styling problem or a length problem. It is a **channel** problem: a single turn carries several different kinds of information — a conclusion, a described pipeline, an audit trail, a pending decision — all serialized into the same read-channel at the same visual weight. The reader has to read ~180 words to find the ~20 that matter, and the pending question routinely ends up buried in gray at the bottom. Explored and rejected: a spatial/canvas paradigm (conversation-as-DAG, node-graph). Interesting for *parallel* content (branches, options) but wrong for *serial* reasoning, and it does not reduce reading — it just scatters it across a plane. The container was never the issue. ## Core model — three channels Information reaches a reader through three channels, and each piece of a Lovebug turn belongs to one: 1. **Read** (serial, high-fidelity, slow) — for *reasoning*. Irreducible. This is the residual "wall" and it is correct for what it carries. No UI removes the read. 2. **Perceive** (parallel, instant, low-fidelity) — for *state*. A verdict line, a pipeline strip, a status. Glanced, not read. 3. **Probe** (interactive, on-demand) — for *detail you might not need*. The justification / evidence, available on expand. The evolution is **not a new medium** — it is routing each piece to the channel that fits its shape. Reasoning stays read. State moves to perceive. Optional detail moves to probe. The pending decision eventually leaves the stream entirely for the rail (ASKS tab). Key insight for the instruction layer: **Lovebug already has this structure in its head when it writes the turn** (it produced the verdict, the flow, the justification, the question). The structure is *destroyed* in the final serialization to uniform prose. We are not asking for a second reasoning pass to *find* structure — only for markers on structure that already exists, and only on turns where it is load-bearing. ## Block types | Type | Channel | Treatment | When | |------|---------|-----------|------| | `verdict` | perceive | accent border-left block, the one-line conclusion | turn has a clear conclusion | | `flow` | perceive | inline pill strip with arrows (reuse node-pill styling) | turn describes a pipeline / sequence | | reasoning / evidence | probe | folded `
`, full fidelity on expand | heavy turns with justification | | `cta` | perceive → rail | bordered block + action buttons; later promotes to ASKS | turn ends awaiting a user decision | | (default prose) | read | today's rendering, optionally dimmed by heuristic | everything else | Light turns ("queued and logged, holding for the Chat-tab worker") get **no** treatment — they stay plain. Sorting only earns its cost on heavy, multi-part turns. ## Build order — renderer FIRST Rationale: writing Lovebug instructions first means Lovebug emits `:::verdict` / `:::cta` markers into a renderer that can't handle them → literal marker garbage in the stream, can't evaluate the idea, wall gets *worse*. "Prove the artifact before building the compiler" — the rendered block is the artifact, the Lovebug instruction is the compiler. Don't teach the vocabulary until it visibly renders. ### Phase 1 — Renderer handlers + styles (build + SEE) - [ ] Add `verdict`, `flow`, `cta` as fence-dispatch handler types alongside existing `json` / `mermaid` handlers (markdown-it fence-dispatch table). - [ ] Style each per the table above; reuse node-pill CSS for `flow`. - [ ] Test against **hand-authored fixtures** — paste a marked-up version of a real heavy turn (e.g. the n8n errorWorkflow explanation) into a test thread. Zero Lovebug involvement. - [ ] **Proof gate:** do sorted turns read better than the wall when hand-fed? If no, STOP here — an afternoon spent, Lovebug untouched. ### Phase 2 — Heuristic floor (cheap, permanent safety net) - [ ] markdown-it rules on structure detectable without understanding meaning: - trailing block ending in `?` → CTA styling - non-bold-led paragraph between structured blocks → dim (scaffolding) - bold-led paragraph → keep full weight (lede) - [ ] Runs on **every** turn including today's unmarked backlog → improves existing history for free, and catches Lovebug's future misses. - [ ] Ship alongside Phase 1. ### Phase 3 — Lovebug instruction addition (emit what renders) - [ ] Only after Phase 1 proof gate passes. - [ ] Add the marker convention to Lovebug's instructions (see "Instruction addition" below). - [ ] Discretionary, heavy-turns-only. Near-zero marginal reasoning cost (marking pre-existing structure, not generating new). ### Phase 4 — CTA → rail (the promote-out-of-stream move) - [ ] `:::cta` block lifts into the ASKS tab as a pending decision object (ties to existing `petal-dispatch-asks-panel` work). - [ ] Biggest change, depends on 1–3 being real. This is the actual "chat → ops console" evolution: not everything Lovebug says is a message; some of it is state that belongs in a perceived surface, not the read-stream. ## Failure mode is graceful Worst case (Lovebug forgets to mark, or the parser misses): you get today's wall. Best case: the sorted version. It degrades **to** the current state, never below it. That property is what makes it safe to ship incrementally — Phases 1+2 are useful with Lovebug completely untouched. The honest limit: the `reasoning` block, once expanded, is still a wall — and that's correct. Channel 1 doing its real job. The win is that you *choose* to open it, about the one thing you distrust, instead of wading through it every turn to find the question at the bottom. ## Fit with existing seams Nothing here is new architecture: - verdict / flow / cta → new fence-dispatch handlers (same pattern as `json` / `mermaid`) - fold → `
` - heuristic floor → markdown-it rules - CTA → rail → existing ASKS panel work ## Instruction addition (Lovebug) — DEFERRED until Phase 1 ships > Do NOT add this to Lovebug's instructions until the renderer handlers from Phase 1 exist and pass the proof gate. Emitting these markers into a renderer that can't parse them produces literal garbage in the stream. Proposed addition: ``` ### Message structure markers (discretionary) On heavy, multi-part turns — where you have a clear conclusion, or you're describing a pipeline, or you're ending on a decision the user must make — mark the structure you already have instead of flattening it into uniform prose. Do NOT mark light or conversational turns; plain prose is correct for those. Never add a second reasoning pass to *find* structure — only mark structure that already exists in what you were going to write. - Conclusion / verdict — the one line that carries the answer: :::verdict The failing workflow passes nothing; n8n injects the error context into Error Trigger automatically. ::: - Pipeline / sequence you're describing — use a flow line: :::flow n8n error -> Error Trigger -> Build Envelope -> SendPBSChat ::: - A decision you're handing back to the user — end the turn with: :::cta Wire it up this way (fix Build Envelope, point at SendPBSChat_New, fire a test error)? ::: Reasoning and evidence (the "why", payloads, code) stays as normal prose between these markers — the renderer folds it. Use at most one verdict and one cta per turn. If a turn has none of these shapes, emit plain prose. ``` Syntax note: `:::name` fence blocks assumed for consistency with described fence-dispatch handlers. Adjust to whatever delimiter the Phase 1 handlers actually register (e.g. fenced ```` ```verdict ```` blocks) — the instruction text must match the shipped parser exactly before it goes live. ## Open questions - Does the journal already emit block-level events mid-turn, or only start/end? (Determines whether the eventual live-narration idea is a journal change or a render change — out of scope here but noted.) - Delimiter choice: `:::verdict` vs fenced code-block style — pick during Phase 1, then reconcile the instruction text. - CTA-in-stream vs CTA-only-in-rail: does the block stay inline AND mirror to ASKS, or move entirely? Decide at Phase 4.