mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix(story-mode): a second opinion is a line in the next panel, not a panel of its own
From review round 9: "Claude asks a stronger AI model for a second
opinion" stood as a whole panel on almost every page, and the reader
never learns what it said (the answer is encrypted). An advisor call now
rides on the panel of the step after it (or the last one, when nothing
follows), counted in `secondOpinion`: a small line under the caption,
"after asking a stronger AI model for a second opinion" ("then asks…"
when it came last), and "· after 1 second opinion" in the Director's
cut. Pages lose a panel of noise and keep room for the work.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
555efef53d
commit
a80638308b
17 files changed
+137
-240
No files matched your search
+30
-1
@@ -2082,7 +2082,19 @@ function groupPanels(events: StoryEvent[]): {
|
||||
const panels: DraftPanel[] = [];
|
||||
let pendingSay: string | undefined;
|
||||
let lastSaid: StoryEvent | undefined;
|
||||
// A second opinion (the advisor) is not a step of its own (its answer is not readable anyway): it
|
||||
// rides on the panel of the step after it, or the last one when nothing follows.
|
||||
let opinions: number[] = [];
|
||||
const takeOpinions = (panel: DraftPanel) => {
|
||||
if (!opinions.length) return;
|
||||
panel.events = [...panel.events, ...opinions].sort((a, b) => a - b);
|
||||
opinions = [];
|
||||
};
|
||||
events.forEach((ev, idx) => {
|
||||
if (ev.tool === 'Advisor') {
|
||||
opinions.push(idx);
|
||||
return;
|
||||
}
|
||||
if (ev.intent === 'say' && !ev.todos) {
|
||||
pendingSay ??= balloonText(ev.label);
|
||||
lastSaid ??= ev;
|
||||
@@ -2098,6 +2110,7 @@ function groupPanels(events: StoryEvent[]): {
|
||||
(events[last.events[0]].tool === 'Advisor') !== (ev.tool === 'Advisor');
|
||||
if (last && last.kind === kind && MERGEABLE.has(kind) && !advisorMix) {
|
||||
last.events.push(idx);
|
||||
takeOpinions(last);
|
||||
if (pendingSay && !last.balloon) last.balloon = { who: 'claude', text: pendingSay };
|
||||
} else {
|
||||
const panel: DraftPanel = { kind, events: [idx] };
|
||||
@@ -2119,10 +2132,16 @@ function groupPanels(events: StoryEvent[]): {
|
||||
if (kind === 'mission') panel.balloon = { who: 'you', text: clip(ev.label, 400) };
|
||||
else if (reportsBack && last && pendingSay) last.balloon = { who: 'claude', text: pendingSay };
|
||||
else if (pendingSay) panel.balloon = { who: 'claude', text: pendingSay };
|
||||
takeOpinions(panel);
|
||||
panels.push(panel);
|
||||
}
|
||||
pendingSay = undefined;
|
||||
});
|
||||
if (opinions.length) {
|
||||
const last = panels[panels.length - 1];
|
||||
if (last) takeOpinions(last);
|
||||
else panels.push({ kind: 'backup', events: opinions });
|
||||
}
|
||||
// The closing words keep a caveat that follows them ("Nothing is committed: this is no git repo.").
|
||||
return {
|
||||
panels,
|
||||
@@ -2282,8 +2301,12 @@ function plannedFailure(turn: StoryTurn, events: StoryEvent[]): false | 'asked'
|
||||
}
|
||||
|
||||
function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, index: number): StoryPanel {
|
||||
const evs = draft.events.map((i) => events[i]);
|
||||
const all = draft.events.map((i) => events[i]);
|
||||
// Second opinions riding on this step are counted, never part of its story.
|
||||
const opinions = all.filter((e) => e.tool === 'Advisor').length;
|
||||
const evs = opinions && opinions < all.length ? all.filter((e) => e.tool !== 'Advisor') : all;
|
||||
const panel: StoryPanel = { kind: draft.kind, events: draft.events, caption: { story: '', director: '' } };
|
||||
if (evs !== all) panel.secondOpinion = opinions;
|
||||
if (draft.balloon) panel.balloon = draft.balloon;
|
||||
const edits = evs.flatMap(editsOf);
|
||||
const checks = evs.filter((e) => e.intent === 'test' || e.intent === 'build');
|
||||
@@ -2666,6 +2689,12 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
case 'complete':
|
||||
break;
|
||||
}
|
||||
if (panel.secondOpinion) {
|
||||
// Asked before the step, or (nothing following) after the last one.
|
||||
const lastWork = Math.max(...draft.events.filter((i) => events[i].tool !== 'Advisor'));
|
||||
const trailing = draft.events.some((i) => events[i].tool === 'Advisor' && i > lastWork);
|
||||
director += ` · ${trailing ? 'then' : 'after'} ${plural(panel.secondOpinion, 'second opinion')}`;
|
||||
}
|
||||
panel.caption = { story, director };
|
||||
return panel;
|
||||
}
|
||||
|
||||
@@ -174,6 +174,8 @@ export interface StoryPanel {
|
||||
removed?: number;
|
||||
ok?: boolean;
|
||||
rounds?: number;
|
||||
/** Second opinions (the advisor) Claude asked for right before this step: not a step of their own. */
|
||||
secondOpinion?: number;
|
||||
/** A loop's test runs in order: whether each passed, `null` for one still running. */
|
||||
roundsOk?: (boolean | null)[];
|
||||
tests?: StoryTests;
|
||||
|
||||
@@ -340,6 +340,19 @@ body.story-open {
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
}
|
||||
.story-cap-note {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
font: 600 10.5px/1.25 var(--story-mono);
|
||||
letter-spacing: 0.01em;
|
||||
text-transform: none;
|
||||
color: var(--story-cap-ink);
|
||||
opacity: 0.78;
|
||||
}
|
||||
.story-cap-note[hidden],
|
||||
.story-overlay.is-director .story-cap-note {
|
||||
display: none;
|
||||
}
|
||||
.story-hit:hover,
|
||||
.story-hit:focus-visible {
|
||||
outline: 3px dashed var(--story-magenta) !important;
|
||||
|
||||
@@ -2375,7 +2375,11 @@ Object.assign(CodemanApp.prototype, {
|
||||
capStory.className = 'story-cap-story';
|
||||
const capDir = document.createElement('span');
|
||||
capDir.className = 'story-cap-dir';
|
||||
cap.append(capStory, capDir);
|
||||
// A second opinion Claude asked for before this step: a line, not a panel of its own.
|
||||
const capNote = document.createElement('span');
|
||||
capNote.className = 'story-cap-note';
|
||||
capNote.hidden = true;
|
||||
cap.append(capStory, capDir, capNote);
|
||||
const balloon = document.createElement('p');
|
||||
balloon.className = 'story-balloon';
|
||||
balloon.hidden = true;
|
||||
@@ -2415,6 +2419,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
panel: null,
|
||||
capStory,
|
||||
capDir,
|
||||
capNote,
|
||||
balloon,
|
||||
liveTag,
|
||||
splash,
|
||||
@@ -2484,6 +2489,15 @@ Object.assign(CodemanApp.prototype, {
|
||||
: 'story-balloon--claude'
|
||||
}`;
|
||||
rt.liveTag.hidden = !p.live;
|
||||
if (rt.capNote) {
|
||||
// Asked before this step, or after it when nothing followed.
|
||||
const work = p.events.filter((i) => page.events[i]?.tool !== 'Advisor');
|
||||
const trailing = p.events.some((i) => page.events[i]?.tool === 'Advisor' && i > Math.max(...work));
|
||||
rt.capNote.hidden = !p.secondOpinion;
|
||||
rt.capNote.textContent = p.secondOpinion
|
||||
? `${trailing ? 'then asks' : 'after asking'} a stronger AI model for a second opinion`
|
||||
: '';
|
||||
}
|
||||
rt.el.classList.toggle('is-live', !!p.live);
|
||||
// Named by its place only: the caption inside is read as the panel's content, once.
|
||||
rt.el.setAttribute('aria-label', `Panel ${index + 1} of ${page.panels.length}`);
|
||||
|
||||
Reference in new issue
Block a user