Skip to content

fix(next): label line 1 only when line 1 is the title - #232

Merged
gcko merged 1 commit into
mainfrom
feature/drc-4270-the-sessions-table-title-label-can-render-with-nothing-after
Aug 27, 2026
Merged

fix(next): label line 1 only when line 1 is the title#232
gcko merged 1 commit into
mainfrom
feature/drc-4270-the-sessions-table-title-label-can-render-with-nothing-after

Conversation

@gcko

@gcko gcko commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Closes DRC-4270 (DRC-4269 finding C1, deferred from #228).

The defect

nextSessionRow builds line 1 as session.title || session.last_prompt || "" but gated the title: label on line 2 existing. The label therefore made a claim about line 1 that line 1's own data need not support:

  1. it captioned an unfiltered last_prompt as a title, and
  2. with neither field present it rendered a dangling title: above a populated line 2, because nextInstructionEchoes returns false on an empty head and nothing else suppressed the line beneath it.

The fix is a conjunction, not a replacement. The existing rule at next-sessions.js:67-71 is also right and had to survive: a row with no line 2 still takes no label, since the label exists only to tell the two lines apart and the column is already headed SESSION.

What the measurement says

I measured before changing anything, and it reframes the issue. Counts only.

Population title + instruction title only no title + instruction
Claude, 3,774 transcripts 1,820 1,918 0
Codex, 459 rollouts 51 230 0

The sets are nested, not coincidentally disjoint: every transcript that publishes an instruction also publishes a title. 36 Claude transcripts have no title and none of them carry an instruction. Codex publishes only the agent (43) and earlier (8) labels, never asked. Published payload today is 6 rows, 3 carrying an instruction, 0 defective.

So this change is behaviour-neutral on every row that exists today, and the test has to construct the state rather than find it.

Why close it anyway

The containment is a property of the corpus on Claude and a property of the code only on Codex.

Codex derives title, last_prompt and instruction from one transcripts.codex_instruction call, so they cannot disagree about whether the read happened.

Claude derives them from genuinely independent reads. last_prompt needs a type: "last-prompt" record inside the bounded tail (claude_data.py:337); session_instruction walks the whole file backward over "user" records. And session_title falls back to prompt_title of the first prompt-signal record and breaks whatever that returns (claude_data.py:161-162). An opening prompt that strips to empty above a genuine later one produces the state — and 386 of 386 local image markers strip to empty (DRC-4269 item 4.10).

The conjunction makes the state unrepresentable rather than unlikely, so nobody has to re-derive this argument.

Out of scope, deliberately

Whether a raw last_prompt on line 1 deserves a label of its own (last prompt, say). 0 rows would show one, and adding a label vocabulary entry on no evidence is the speculative half of the issue.

Verification

The new test fails against main showing the defect verbatim — title</span>: Deploy the staging store captioning a raw prompt — and passes here. One assertion of my own was replaced during review of my own work: <strong></strong> would have passed for free because the live dot occupies that element on the fixture, so it is now the actual dangling-colon signature ": </strong>".

Byte pins recomputed from the assets, never hand-edited:

  • next-sessions.js 4,557 -> 5,346
  • assembled next page 231,321 -> 232,110

Both deltas are 789, which is the cross-check that the page pin moved by exactly the part's growth and nothing else changed underneath.

Full pre-PR suite green: ruff, ruff format --check, mypy --strict (108 files), lint_embedded.py (node present, so the JS syntax check really ran), validate_plugins.py, bump_version.py --current (0.16.0, no manifest in the diff), 1,935 dashboard tests + 192 scripts tests OK, coverage 90.7% against a threshold of 73.

`nextSessionRow` builds line 1 as `session.title || session.last_prompt || ""`
but gated the `title:` label on line 2 existing, so the label made a claim line
1's own data need not support. Two ways: it captioned an unfiltered
`last_prompt` as a title, and with neither field present it rendered a dangling
`title: ` above a populated line 2 — `nextInstructionEchoes` returns false on an
empty head, so nothing else suppressed the line beneath it.

The gate is now a conjunction rather than a replacement, because the existing
rule is also right and had to survive: a row with no line 2 still takes no
label, since the label exists only to tell the two lines apart and the column is
already headed SESSION.

Measured before changing anything. Over 3,774 local Claude transcripts and 459
Codex rollouts, the precondition occurs 0 times and the sets are NESTED: all
1,820 Claude transcripts publishing an instruction also publish a title, and all
51 Codex ones do. So this is behaviour-neutral on every row that exists today,
and the test has to construct the state rather than find it.

It is worth closing anyway because the containment is a property of the corpus
on Claude and of the code only on Codex. Codex derives title, last_prompt and
instruction from one `codex_instruction` call, so they cannot disagree. Claude
derives them from independent reads: `last_prompt` needs a `last-prompt` record
inside the bounded tail while `session_instruction` walks the whole file
backward, and `session_title` falls back to `prompt_title` of the FIRST
prompt-signal record and breaks whatever that returns. An opening prompt that
strips to empty above a genuine later one produces the state, and 386 of 386
local image markers strip to empty. The conjunction makes it unrepresentable
instead of unlikely.

Out of scope deliberately: whether a raw `last_prompt` on line 1 deserves a
label of its own. 0 rows would show one, and a new label vocabulary entry on no
evidence is speculation.

Byte pins recomputed from the assets: next-sessions.js 4,557 -> 5,346 and the
assembled next page 231,321 -> 232,110. The two deltas are both 789, which is
the cross-check that nothing else moved underneath.

Closes DRC-4270

Signed-off-by: Jared Scott <jared.scott@variable.team>
@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Name                                                                  Stmts   Miss Branch BrPart  Cover
-------------------------------------------------------------------------------------------------------
cargento/skills/cargento/agy_hook.py                                     79     14     28      7  78.5%
cargento/skills/cargento/cargento_runtime/__init__.py                     0      0      0      0 100.0%
cargento/skills/cargento/cargento_runtime/aggregate.py                  212      1     64      0  99.6%
cargento/skills/cargento/cargento_runtime/asks.py                       110      0     28      0 100.0%
cargento/skills/cargento/cargento_runtime/claude_data.py                305     33    142     16  89.0%
cargento/skills/cargento/cargento_runtime/cli.py                        126     14     26      3  87.5%
cargento/skills/cargento/cargento_runtime/collectors/__init__.py          0      0      0      0 100.0%
cargento/skills/cargento/cargento_runtime/collectors/antigravity.py     410     41    166     25  87.2%
cargento/skills/cargento/cargento_runtime/collectors/claude.py          261     19     94     12  90.7%
cargento/skills/cargento/cargento_runtime/collectors/codex.py            98      7     38      7  89.7%
cargento/skills/cargento/cargento_runtime/collectors/copilot.py         148      6     50      2  96.0%
cargento/skills/cargento/cargento_runtime/collectors/cursor.py          277     20    106     17  89.8%
cargento/skills/cargento/cargento_runtime/collectors/droid.py            32      3      6      1  89.5%
cargento/skills/cargento/cargento_runtime/collectors/gemini.py           53      7     16      4  84.1%
cargento/skills/cargento/cargento_runtime/collectors/goose.py            89     11     28      4  87.2%
cargento/skills/cargento/cargento_runtime/collectors/opencode.py         78      6     26      2  92.3%
cargento/skills/cargento/cargento_runtime/collectors/pi.py              326     34    152     20  88.7%
cargento/skills/cargento/cargento_runtime/config.py                     187      1     20      1  99.0%
cargento/skills/cargento/cargento_runtime/diagnostics.py                 84      4     26      4  92.7%
cargento/skills/cargento/cargento_runtime/dismissals.py                 113      2     28      2  97.2%
cargento/skills/cargento/cargento_runtime/events.py                     162      0     62      0 100.0%
cargento/skills/cargento/cargento_runtime/http_api.py                   515     31    176      9  94.2%
cargento/skills/cargento/cargento_runtime/io.py                         126      2     28      0  98.7%
cargento/skills/cargento/cargento_runtime/lifecycle.py                  323     15    104      6  95.1%
cargento/skills/cargento/cargento_runtime/notifications.py              174     14     60      4  91.5%
cargento/skills/cargento/cargento_runtime/observation.py                235      2     64      0  99.3%
cargento/skills/cargento/cargento_runtime/observer.py                   249     34    110     13  84.7%
cargento/skills/cargento/cargento_runtime/probe.py                       44      0     18      1  98.4%
cargento/skills/cargento/cargento_runtime/quota.py                      333      2    112      1  99.3%
cargento/skills/cargento/cargento_runtime/records.py                    256      5    114      9  96.2%
cargento/skills/cargento/cargento_runtime/sessions.py                   101      0     44      0 100.0%
cargento/skills/cargento/cargento_runtime/snapshot.py                    36      0      4      0 100.0%
cargento/skills/cargento/cargento_runtime/spacedock.py                  397     46    200     24  87.9%
cargento/skills/cargento/cargento_runtime/state.py                       64      0      2      0 100.0%
cargento/skills/cargento/cargento_runtime/stream.py                      57      0      8      0 100.0%
cargento/skills/cargento/cargento_runtime/transcripts.py                361     16    192     15  94.4%
cargento/skills/cargento/cargento_runtime/turns.py                      197     14    104     12  90.7%
cargento/skills/cargento/cargento_runtime/web/__init__.py                 0      0      0      0 100.0%
cargento/skills/cargento/cargento_runtime/web/page.py                    54      0     14      0 100.0%
cargento/skills/cargento/event_hook.py                                   86      4     28      3  93.9%
cargento/skills/cargento/mcp_server.py                                  377     22    112     14  92.2%
cargento/skills/cargento/notify_hook.py                                  49     15      6      1  67.3%
cargento/skills/cargento/server.py                                        3      0      2      1  80.0%
cargento/skills/cargento/statusline_hook.py                             131     13     46      8  87.0%
scripts/bench_collect.py                                                211     10     54      6  94.0%
scripts/bench_event_latency.py                                           67     67     14      0   0.0%
scripts/bump_version.py                                                  60     12     24      5  77.4%
scripts/capture_hook.py                                                 287     30     86     11  88.5%
scripts/derive_prompt_shapes.py                                         210     16     88     14  89.3%
scripts/lint_embedded.py                                                 92      3     28      2  95.8%
scripts/validate_plugins.py                                             663    186    386     58  69.3%
-------------------------------------------------------------------------------------------------------
TOTAL                                                                  8908    782   3334    344  89.7%

Threshold: fail_under in pyproject.toml · label coverage-exception to bypass (visible in PR timeline).

@gcko
gcko merged commit b49a608 into main Aug 27, 2026
12 checks passed
@gcko
gcko deleted the feature/drc-4270-the-sessions-table-title-label-can-render-with-nothing-after branch August 27, 2026 23:38
gcko added a commit that referenced this pull request Aug 28, 2026
…sync marker (#234)

Two loose ends from cutting v0.17.0, the first real run of `cargento-release`.

Step 5 said `git tag "v$VERSION"`. On a machine with `tag.gpgsign true` that tries
to create a signed tag, finds no message, and dies with `no tag message?` before
anything reaches the remote. It failed on the release it was written for.

The fix is `git -c tag.gpgsign=false`, which forces the lightweight form. That is
what this repository's release tags are (`git cat-file -t v0.16.0` prints
`commit`, not `tag`) and it is what the workflow's own `git tag -f` re-creates
when it moves the tag onto the bump commit, so the override matches the end state
rather than working around it. Where signing is off it does nothing. A
`git cat-file -t` check rides along because the wrong outcome is silent in the
other direction: an annotated tag pushes fine and the mismatch only shows up
later.

Rehearsed this time rather than reasoned about: created a throwaway tag locally,
confirmed `commit`, deleted it, and confirmed the bare form still fails. The
comment in the skill records why that rehearsal is possible and why the original
command was the one that went untested.

Also stamps `docs-synced-through` from `0f9d086` to `ec10918`, per AGENTS.md's
rule to advance it once from main after merges rather than per branch. The range
covers four commits: the previous stamp (#231), the sessions-table title label
(#232), the `cargento-release` skill itself (#233), and the v0.17.0 release bump.

Signed-off-by: Jared Scott <jared.scott@variable.team>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant