Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions docs/user/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,18 +308,19 @@ stroke buttons under each assistant message. Click it to expand a
panel anchored to that turn. The panel shows:

- A header pill marking the cohort as **confirmed**,
**disconfirmed**, **waiting**, or **aged out**, depending on what
the reaction classifier did with it on the following turn.
- The **predictions that fired** for that turn, grouped by theme
(paraphrase clusters collapse into a representative with a
"+N similar" chevron). "Show all" bypasses the clustering and
**disconfirmed**, or **pending**, depending on what the reaction
classifier did with it on the following turn.
- The **substrate row** for the same round, lifted to the top of the
panel with an accent stripe because it's the worker's after-the-
fact summary of what actually happened on this turn ("user asked
X about Y, expressing Z" / "the assistant did W and it landed P").
Includes its lifecycle state (pending assimilation, assimilated
but unembedded, or fully baked) and the round's valence reading.
- Below that, the **predictions that fired** for that turn, grouped
by theme (paraphrase clusters collapse into a representative with
a "+N similar" chevron). "Show all" bypasses the clustering and
lists every fire individually. Each entry shows its tier, ranking
score, valence, confidence, and health.
- The **substrate row** for the same round - the worker's structured
summary of what happened ("user asked X about Y, expressing Z"
/ "the assistant did W and it landed P"), with its lifecycle
state (pending assimilation, assimilated but unembedded, or fully
baked) and the round's valence reading.

The icon only appears on messages that produced at least one fire
or substrate row, so cold-start messages and any turn the worker
Expand Down
110 changes: 60 additions & 50 deletions src/components/CohortPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -123,20 +123,18 @@
return `${sign}${v.toFixed(2)}`;
}

// Three-state resolution label. Null means the reaction classifier
// hasn't scored this cohort yet - distinguish in-flight from aged-
// out by the 10-minute resolution window the classifier uses.
function resolutionLabel(
confirmed: boolean | null,
firedAtIso: string | null
): string {
// Three-state resolution. The old four-way label (in-flight /
// window-open / aged-out) earned its keep in the diagnostics
// modal where cohorts were a flat list with no message context;
// inline under the user message that fired them, the transcript
// already encodes "this turn fired N exchanges ago" - if a later
// user turn is visible the classifier had its shot, and if it
// didn't, the cohort is the latest one. "pending" covers every
// unresolved state.
function resolutionLabel(confirmed: boolean | null): string {
if (confirmed === true) return 'confirmed';
if (confirmed === false) return 'disconfirmed';
if (!firedAtIso) return 'pending';
const ageMs = Date.now() - new Date(firedAtIso).getTime();
if (ageMs < 60 * 1000) return 'waiting (in-flight)';
if (ageMs < 10 * 60 * 1000) return 'waiting (resolution window open)';
return 'aged out (no reaction)';
return 'pending';
}

function resolutionStatusClass(confirmed: boolean | null): string {
Expand Down Expand Up @@ -164,7 +162,7 @@
<span
class="cohort-status status-{resolutionStatusClass(wasConfirmed)}"
>
{resolutionLabel(wasConfirmed, firedAt)}
{resolutionLabel(wasConfirmed)}
</span>
<span class="cohort-count">
{#if collapsed && !raw}
Expand All @@ -188,6 +186,45 @@
{/if}
</header>

{#if substrate}
<!-- Substrate stub for the same round, mounted ABOVE the fires
because it's the worker's after-the-fact summary of what
actually happened on this turn ("user asked X about Y,
expressing Z" / "the assistant did W and it landed P") -
the headline read. The fires below are the predictions that
fed into producing this turn; the substrate is the result.
Visually emphasised with a left-edge accent stripe and a
tinted background so the panel reads as "answer up top,
working below" rather than a flat list of equal-weight
sections. Lifecycle status tracks how far the formation
worker has carried the row - situation/outcome filled by
the assimilator, embedding model filled by the embedder. -->
<div class="substrate-block">
<header class="substrate-head">
<span class="substrate-label">substrate</span>
<span
class="substrate-status status-{substrateStatusClass(substrate)}"
>
{assimilationStatus(substrate)}
</span>
{#if substrate.valence !== null}
<span class="substrate-meta">
valence {formatValence(substrate.valence)}
</span>
{/if}
</header>
{#if substrate.situation}
<p class="substrate-situation">{substrate.situation}</p>
{/if}
{#if substrate.outcome}
<p class="substrate-outcome subtle">
<em>Outcome:</em>
{substrate.outcome}
</p>
{/if}
</div>
{/if}

{#if raw}
<ul class="fire-list">
{#each sortedFires as fire (fire.id)}
Expand Down Expand Up @@ -229,39 +266,6 @@
{/each}
</ul>
{/if}

{#if substrate}
<!-- Substrate stub for the same round. Lives in the panel because
it shares the same per-user-message anchor: the chat loop
writes both at the boundaries of one turn. Lifecycle status
tracks how far the formation worker has carried the row -
situation/outcome filled by the assimilator, embedding model
filled by the embedder. -->
<div class="substrate-block">
<header class="substrate-head">
<span class="substrate-label">substrate</span>
<span
class="substrate-status status-{substrateStatusClass(substrate)}"
>
{assimilationStatus(substrate)}
</span>
{#if substrate.valence !== null}
<span class="substrate-meta">
valence {formatValence(substrate.valence)}
</span>
{/if}
</header>
{#if substrate.situation}
<p class="substrate-situation">{substrate.situation}</p>
{/if}
{#if substrate.outcome}
<p class="substrate-outcome subtle">
<em>Outcome:</em>
{substrate.outcome}
</p>
{/if}
</div>
{/if}
</div>

{#snippet fireRow(fire: SamskaraFireDiagnosticRow)}
Expand Down Expand Up @@ -434,12 +438,18 @@
font-weight: 700;
}

/* Substrate emphasised as the headline read of the turn: left-
edge accent stripe + faintly tinted background lift it visually
above the fire list that follows. Padded inside the stripe so
the text doesn't crowd against the accent. */
.substrate-block {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding-top: 0.5rem;
border-top: 1px dashed var(--border);
padding: 0.45rem 0.6rem 0.5rem;
border-left: 3px solid var(--accent);
border-radius: 0 4px 4px 0;
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.substrate-head {
display: flex;
Expand All @@ -451,8 +461,8 @@
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--muted);
font-weight: 600;
color: var(--accent);
font-weight: 700;
}
.substrate-meta {
font-size: 0.72rem;
Expand Down
Loading