Skip to content

Commit 8c8ef51

Browse files
jacekradkoclaude
andauthored
Lead the page with what the change is, and count items in one word (#95)
Three things cold readers tripped on after #94 (DD-5716, DD-5718, DD-5719). The reconstruction now renders under the recommendation as "What this change is", in both the live and final page, and leaves the folded frame, which keeps the claim check and the reviewer's two answers, now labelled as theirs. Every count on the page says "item" and "read" where it said "beat" and "walked"; the store, the `beat` JSON, CSS classes and `data-action="next"` are untouched, so this is page vocabulary only. The inclusion caveat names its referent: "1 finding included in the review. Including a finding does not fix the issue it describes." 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/radkode/codesmith/underwrite/pr/95?autoLogin=true&ref=codesmith_pr_footer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1793448351&installation_model_id=433329&pr_number=95&ref=codesmith_pr_footer&repository=radkode%2Funderwrite&return_to=https%3A%2F%2Fgithub.com%2Fradkode%2Funderwrite%2Fpull%2F95&signature=5f74f76078956fc390a5c8ed5b1822dc19f072a15fd63b9c71115c6073d2c96d"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a> <sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you need. Autofix is disabled.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 8ed453c commit 8c8ef51

5 files changed

Lines changed: 75 additions & 57 deletions

File tree

‎README.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ handling untrusted artifacts. Neither the gateway nor the linked implementation
158158
A walk serves itself on loopback, and the page is where you actually review. While a walk
159159
is listening, the beat being read leads the page on its own, under a bar that stays in
160160
view: the plan as a track with the current beat marked, the agent's own sentence about
161-
what it is doing, and Next beat. Everything already walked sits below, ordered by what is
161+
what it is doing, and Next item. Everything already walked sits below, ordered by what is
162162
owed rather than by what was walked: open flags expanded at the top, resolved beats folded
163163
to one line with your call on it, clean beats to one line each that still carries its
164164
proof. When the walk ends, that ledger is the page, and it is what the final render keeps.
@@ -168,17 +168,17 @@ that finished.
168168
The final render stays on your machine unless you agree to publish it, as **Finish** says.
169169
The keys do what the row under the beat says: Enter saves a note, or records a decision
170170
when the note is the decision; ⌘Enter or Ctrl+Enter fires the row's primary action; `n`
171-
is Next beat and `f` goes to the first open flag.
171+
is Next item and `f` goes to the first open flag.
172172
Resolving a beat asks twice, because none of it can be walked back: the store lets accept
173173
and drop out of an open flag only, and refuses to re-decide a beat that carries a delivery.
174174
So Implement, Include, Record decision and Drop all arm on the first click and send on the
175175
second, with the prompt naming which beat. Anything else answers no, including three
176-
seconds of nothing. Save note and Next beat go straight through.
176+
seconds of nothing. Save note and Next item go straight through.
177177

178178
Decisions happen there too. Review mode uses Include in review and Drop. Report mode uses
179179
Include in report and Drop. Local branch and working-tree sessions retain Implement. A
180180
policy question whose answer completes the work carries Record decision.
181-
Each has a field for putting the call in your own words, and Next beat advances the walk
181+
Each has a field for putting the call in your own words, and Next item advances the walk
182182
from anywhere. Clicking is what unblocks the terminal side, which parks on the server
183183
between beats rather than spinning. The terminal still takes the same answers in words,
184184
so closing the tab never strands a session.

‎skills/underwrite/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ ordinary flag shows Implement for local `branch` targets or Include in review in
332332
mode, or Include in report in `report` mode, plus Drop. A legacy or malformed PR in branch
333333
mode shows that implementation is blocked instead. A decision-only flag with top-level
334334
`resolution_kind: "decision"` shows Record decision. Save note remains available on any
335-
beat, and Next beat works anywhere. The page writes its URL to `$R/serve.json`.
335+
beat, and Next item works anywhere. The page writes its URL to `$R/serve.json`.
336336

337337
Those labels name the effect while the durable protocol remains stable. Implement,
338338
Include in review, and Include in report all post the canonical `accept` action.

‎skills/underwrite/assets/report.css‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ h1 .num { color: var(--ink-3); font-variant-numeric: tabular-nums; }
193193
.assessment .recommendation { font-size: 19px; line-height: 1.5; }
194194
.assessment .decisions-owed { font-weight: 600; }
195195
.assessment .qualification { color: var(--ink-2); font-size: 14px; }
196+
.assessment h3 { margin: 20px 0 6px; font-size: 15px; line-height: 1.3; }
197+
.assessment .change { color: var(--ink-2); }
196198
.decision { max-width: 70ch; margin-bottom: 20px; }
197199
.decision p { margin: 0 0 6px; }
198200

‎skills/underwrite/scripts/render-report.py‎

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@
5353

5454
# (heading, hint, states, expanded by default)
5555
SECTIONS = (
56-
("Needs your call", "out of beat order, on purpose", ("flag",), True),
56+
("Needs your call", "out of item order, on purpose", ("flag",), True),
5757
("Accepted", "your call, and what came of it", ("accepted", "decided"), True),
58-
("Walked and clean", "nothing owed, proof on each", ("clean", "unverified"), False),
58+
("Read and clean", "nothing owed, proof on each", ("clean", "unverified"), False),
5959
("Dropped", "raised, then set aside", ("dropped",), False),
6060
)
6161

6262
LANDS_TAG = {"landed": "Landed", "ready": "Ready", "open": "Your call"}
63-
FRAME_PROSE = (("reconstruction", "Reconstruction"), ("claim_check", "Claim check"))
64-
FRAME_CALLS = (("orient_call", "Orient"), ("plan_call", "Plan"))
63+
FRAME_PROSE = (("claim_check", "Claim check"),)
64+
FRAME_CALLS = (("orient_call", "Your answer on the summary"), ("plan_call", "Your answer on the order"))
6565

6666
# Only for --standalone. The viewport tag is load-bearing: report.css has a
6767
# 620px breakpoint that never fires without it.
@@ -113,7 +113,7 @@
113113
disarm();
114114
const label = button.textContent;
115115
const verb = label.split(' ')[0];
116-
const which = n === 'walk' ? '' : ` beat ${n}`;
116+
const which = n === 'walk' ? '' : ` item ${n}`;
117117
armed = {
118118
button,
119119
label,
@@ -131,7 +131,7 @@
131131
// stored, so it is not the reviewer's to repeat; a walk that failed or stopped still
132132
// needs telling, which is the one thing the page cannot do.
133133
const NOUNS = {
134-
accept: 'decision', drop: 'decision', decide: 'decision', next: 'next beat',
134+
accept: 'decision', drop: 'decision', decide: 'decision', next: 'next item',
135135
};
136136
function held(kind, headState) {
137137
const noun = NOUNS[kind] || kind || 'call';
@@ -811,7 +811,7 @@ def beat_html(
811811
body.append(diff_html(beat["diff"]))
812812
if beat.get("call"):
813813
body.append(
814-
'<div class="call"><span class="lbl">Your call · beat '
814+
'<div class="call"><span class="lbl">Your call · item '
815815
f'{n}</span><q>{md(beat["call"])}</q></div>'
816816
)
817817
delivery = delivery_html(beat, mode, replacement, untrusted_pr)
@@ -887,16 +887,16 @@ def beat_html(
887887
# and says what the keys do, once, where the reviewer is acting.
888888
advance = keys = ""
889889
if stage:
890-
advance = '<button class="act" data-action="next">Next beat</button>'
890+
advance = '<button class="act" data-action="next">Next item</button>'
891891
hints = [f"<kbd>Enter</kbd> {enter}"]
892892
if mod:
893893
hints.append(f'<kbd><span data-mod>⌘</span>Enter</kbd> {mod}')
894-
hints.append("<kbd>n</kbd> next beat")
894+
hints.append("<kbd>n</kbd> next item")
895895
keys = f'<span class="keys">{" · ".join(hints)}</span>'
896896
# The note comes first so Tab lands on the primary right after typing.
897897
body.append(
898898
f'<div class="acts" data-acts="{attr(n)}">'
899-
f'<input class="note" aria-label="your words, beat {attr(n)}" placeholder="{placeholder}">'
899+
f'<input class="note" aria-label="your words, item {attr(n)}" placeholder="{placeholder}">'
900900
f'{controls}{advance}<span class="act-msg" role="status"></span>{keys}</div>'
901901
)
902902

@@ -950,7 +950,7 @@ def coverage_html(session, beats):
950950
return ""
951951
left = [e for e in entries if e["state"] is None]
952952
walked = len(entries) - len(left)
953-
head = f"{walked} of {len(entries)} beats walked"
953+
head = f"{walked} of {len(entries)} items read"
954954
if not left:
955955
return f'<p class="coverage is-done">{md(head)}.</p>'
956956
rows = []
@@ -965,7 +965,7 @@ def coverage_html(session, beats):
965965
f'{md(textwrap.shorten(what, 88, placeholder="…"))}</span></li>'
966966
)
967967
return (
968-
f'<div class="coverage"><p>{md(head)}. Not walked:</p>'
968+
f'<div class="coverage"><p>{md(head)}. Not read yet:</p>'
969969
f'<ul class="cov-left">{"".join(rows)}</ul></div>'
970970
)
971971

@@ -979,15 +979,15 @@ def bar_html(session, beats, current, phase):
979979
for entry in entries:
980980
state = entry["state"]
981981
if state is None:
982-
cls, word = "is-todo", "not yet walked"
982+
cls, word = "is-todo", "not read yet"
983983
else:
984984
suffix, token = STATE_STYLE.get(state, ("unver", "UNVERIFIED"))
985985
cls, word = f"s-{suffix}", token.lower()
986986
now = entry["n"] == current_n
987987
current_attr = ' aria-current="step"' if now else ""
988988
title = " · ".join(
989989
part for part in (
990-
f"beat {entry['n']}", entry["tier"], entry["where"], word
990+
f"item {entry['n']}", entry["tier"], entry["where"], word
991991
) if part
992992
)
993993
marks.append(
@@ -996,17 +996,17 @@ def bar_html(session, beats, current, phase):
996996
f"{md(entry['n'])}</li>"
997997
)
998998
if current_n:
999-
position = f"beat {current_n} of {total}" if total else f"beat {current_n}"
999+
position = f"item {current_n} of {total}" if total else f"item {current_n}"
10001000
elif phase == "done":
1001-
position = f"{len(beats)} of {total} walked" if total else f"{len(beats)} walked"
1001+
position = f"{len(beats)} of {total} read" if total else f"{len(beats)} read"
10021002
else:
1003-
position = "waiting for beat 1"
1003+
position = "waiting for item 1"
10041004
return (
10051005
'<div class="bar">'
10061006
f'<ol class="track" aria-label="{attr(position)}">{"".join(marks)}</ol>'
10071007
'<span id="live" class="live starting" role="status">connecting</span>'
10081008
'<div class="acts walk" data-acts="walk">'
1009-
'<button class="act" data-action="next">Next beat</button>'
1009+
'<button class="act" data-action="next">Next item</button>'
10101010
'<span class="act-msg" role="status"></span></div>'
10111011
"</div>"
10121012
)
@@ -1022,17 +1022,17 @@ def stage_html(session, beats, current, card):
10221022
if current:
10231023
hint = " · ".join(
10241024
part for part in (
1025-
f"beat {current_n} of {total}" if total else f"beat {current_n}",
1025+
f"item {current_n} of {total}" if total else f"item {current_n}",
10261026
current.get("tier", ""),
10271027
) if part
10281028
)
10291029
else:
1030-
hint = "nothing walked yet"
1030+
hint = "nothing read yet"
10311031
ghost = ""
10321032
if upcoming:
10331033
line = " · ".join(
10341034
part for part in (
1035-
f"beat {upcoming['n']} of {total}", upcoming["tier"], upcoming["where"]
1035+
f"item {upcoming['n']} of {total}", upcoming["tier"], upcoming["where"]
10361036
) if part
10371037
)
10381038
ghost = (
@@ -1068,21 +1068,26 @@ def assessment(session, beats, problems_by_n, validation_problems=()):
10681068
label = "Provisional recommendation" if limits else "Recommendation"
10691069
if execution_mode(session) == "no_exec":
10701070
limits.append("Static inspection only; runtime behavior was not verified.")
1071-
if session_mode(session) in ("review", "report") and any(
1072-
beat.get("state") == "accepted" for beat in beats
1073-
):
1074-
limits.append("Inclusion does not mean the underlying issue was fixed.")
1071+
mode = session_mode(session)
1072+
included = sum(beat.get("state") == "accepted" for beat in beats)
1073+
if mode in ("review", "report") and included:
1074+
limits.append(
1075+
f"{included} finding{'s' if included != 1 else ''} included in the {mode}. "
1076+
"Including a finding does not fix the issue it describes."
1077+
)
10751078
owed = sum(beat.get("state") == "flag" for beat in beats)
10761079
return label, text, limits, owed
10771080

10781081

10791082
def assessment_html(session, beats, problems_by_n, validation_problems=()):
10801083
label, text, limits, owed = assessment(session, beats, problems_by_n, validation_problems)
1084+
change = session.get("reconstruction")
10811085
return (
10821086
'<section class="assessment" aria-label="Review recommendation">'
10831087
f'<h2>{label}</h2><p class="recommendation">{md(text)}</p>'
10841088
f'<p class="decisions-owed">{owed} decision{"s" if owed != 1 else ""} owed</p>'
10851089
+ "".join(f'<p class="qualification">{md(limit)}</p>' for limit in limits)
1090+
+ (f'<h3>What this change is</h3><p class="change">{md(change)}</p>' if change else "")
10861091
+ '</section>'
10871092
)
10881093

@@ -1133,7 +1138,7 @@ def body_html(
11331138
("is-flag", counts.get("flag", 0), "needs your call"),
11341139
("is-acc", counts.get("accepted", 0) + counts.get("decided", 0), "accepted"),
11351140
("is-drop", counts.get("dropped", 0), "dropped"),
1136-
("is-mute", len(beats), "beats walked"),
1141+
("is-mute", len(beats), "items read"),
11371142
]
11381143
parts.append(
11391144
'<div class="counts">'
@@ -1205,7 +1210,7 @@ def frame_html(session, live):
12051210
return ""
12061211
summary = '<span class="sep">·</span>'.join(
12071212
f'<span class="frame-k">{label}</span><q>{md(text)}</q>' for label, text in calls
1208-
) or '<span class="frame-k">Orient</span>'
1213+
) or '<span class="frame-k">How this review was framed</span>'
12091214
body = "".join(f"<dt>{label}</dt><dd>{md(text)}</dd>" for label, text in prose)
12101215
if body:
12111216
body = f"<dl>{body}</dl>"

0 commit comments

Comments
 (0)