mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
fix(story-mode): a phone you can use with a thumb, and saves that say what they saved
From review round 5 (a phone with touch):
- Touch screens: every control at least 44 px (prev/next, close, the
Story/Director's cut toggle, Save image, the final panel's chips, drawer
buttons and links), and the small type at 13 px.
- The drawer on a phone starts below the bar (it cut through it), closes by
dragging its head down, and keeps its head small in landscape.
- The turn picker says where you are ("Turn 2 of 5"), learns about new
turns while you look at an older one, and a turn you picked yourself is
not announced as news.
- A check whose command also wrote files offers "See the 2 files it wrote".
- Test counts are read from the whole output: a later command's lines had
pushed pytest's "5 passed" out of the kept tail.
- A save names its commit: "Claude saves the work: "feat: add wrap() for
modular wrapping into a range"." (from -m, -am, --message or the
here-document of git commit -F -; a trailer is never the subject).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
cdb7b5d55f
commit
3f1ecfe83a
11 files changed
+118
-22
No files matched your search
@@ -731,10 +731,10 @@ body.story-open .toast-container {
|
||||
padding-right: max(16px, env(safe-area-inset-right, 0px));
|
||||
}
|
||||
.story-drawer {
|
||||
top: auto;
|
||||
top: calc(var(--story-bar-h, 18vh) + 6px);
|
||||
left: 0;
|
||||
width: auto;
|
||||
height: 82vh;
|
||||
height: auto;
|
||||
border-left: 0;
|
||||
border-top: 3px solid var(--story-ink);
|
||||
padding-left: env(safe-area-inset-left, 0px);
|
||||
@@ -933,6 +933,42 @@ body.story-open .toast-container {
|
||||
content: '✓ ';
|
||||
}
|
||||
|
||||
/* Touch screens: every control a finger can hit (44 px), and the small type a little larger. */
|
||||
@media (pointer: coarse) {
|
||||
.story-btn,
|
||||
.story-drawer-close {
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
}
|
||||
.story-seg-btn,
|
||||
.story-save,
|
||||
.story-btn-s,
|
||||
.story-chips li > * {
|
||||
min-height: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.story-link {
|
||||
padding-block: 6px;
|
||||
}
|
||||
.story-cap-dir,
|
||||
.story-ev,
|
||||
.story-code,
|
||||
.story-pre {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
/* A phone on its side: the sheet's head takes less of the little height there is. */
|
||||
@media (max-height: 480px) {
|
||||
.story-drawer-head {
|
||||
gap: 2px;
|
||||
padding-block: 6px;
|
||||
}
|
||||
.story-drawer-sub {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.story-fresh *,
|
||||
.story-arrive,
|
||||
|
||||
@@ -1702,6 +1702,18 @@ Object.assign(CodemanApp.prototype, {
|
||||
drawer.hidden = true;
|
||||
drawer.setAttribute('role', 'dialog');
|
||||
const drawerHead = el('header', 'story-drawer-head');
|
||||
// A sheet comes down the way it went up: drag its head down to close it.
|
||||
let dragFrom = null;
|
||||
drawerHead.addEventListener('touchstart', (e) => (dragFrom = e.touches[0]?.clientY ?? null), { passive: true });
|
||||
drawerHead.addEventListener(
|
||||
'touchend',
|
||||
(e) => {
|
||||
const y = e.changedTouches[0]?.clientY;
|
||||
if (dragFrom !== null && y !== undefined && y - dragFrom > 70) this._storyCloseDrawer();
|
||||
dragFrom = null;
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
const drawerKind = el('span', 'story-drawer-kind');
|
||||
const drawerLive = el('span', 'story-live story-drawer-live');
|
||||
drawerLive.append(el('i'), el('span', null, 'LIVE'));
|
||||
@@ -1808,6 +1820,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
|
||||
_storyPickTurn(id) {
|
||||
const st = this._story;
|
||||
st.picked = true; // a turn you chose is not news to announce
|
||||
const turns = st.data?.turns || [];
|
||||
st.turnId = id && id !== turns[turns.length - 1]?.id ? id : null;
|
||||
st.first = true;
|
||||
@@ -1947,7 +1960,8 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
// A finished newest page: look again now and then (a local command like /compact raises no
|
||||
// hook). An unchanged page is a 304.
|
||||
if (!st.turnId && Date.now() - (st.lastIdlePoll || 0) > STORY_IDLE_POLL_MS) {
|
||||
// Parked on an older turn: the turn list still learns about new turns now and then.
|
||||
if (Date.now() - (st.lastIdlePoll || 0) > STORY_IDLE_POLL_MS) {
|
||||
st.lastIdlePoll = Date.now();
|
||||
this._storyRefresh();
|
||||
}
|
||||
@@ -2066,6 +2080,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (page) sel.value = page.id;
|
||||
sel.disabled = turns.length < 2;
|
||||
const idx = page ? turns.findIndex((t) => t.id === page.id) : -1;
|
||||
sel.setAttribute('aria-label', idx >= 0 ? `Turn ${idx + 1} of ${turns.length}` : 'Turn');
|
||||
st.prev.disabled = idx <= 0;
|
||||
st.next.disabled = idx < 0 || idx >= turns.length - 1;
|
||||
st.live.hidden = !(page && !page.complete && !page.interrupted && !st.turnId);
|
||||
@@ -2102,7 +2117,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
const first = st.announced === undefined;
|
||||
const newTurn = !first && !st.announced.startsWith(`${page.id}|`);
|
||||
st.announced = key;
|
||||
if (first) return;
|
||||
const picked = st.picked;
|
||||
st.picked = false;
|
||||
if (first || (picked && newTurn)) return;
|
||||
const last = page.panels[page.panels.length - 1];
|
||||
const finished = `${done ? `Turn finished. ${last?.caption.story || ''}` : ''}`.trim();
|
||||
box.textContent = newTurn ? `New turn: ${page.prompt.slice(0, 140)}${finished ? `. ${finished}` : ''}` : finished;
|
||||
@@ -2893,6 +2910,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
st.drawerSeen = new Set();
|
||||
st.drawerEl.hidden = false;
|
||||
st.root.classList.add('has-drawer');
|
||||
// On a phone the drawer is a sheet from the bottom: it starts below the bar, whatever its height.
|
||||
const bar = st.root.querySelector('.story-bar');
|
||||
if (bar) st.root.style.setProperty('--story-bar-h', `${bar.offsetHeight}px`);
|
||||
this._storyRenderDrawer(true);
|
||||
st.drawerEl.querySelector('.story-drawer-close')?.focus({ preventScroll: true });
|
||||
},
|
||||
@@ -3251,6 +3271,17 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (ev.output !== undefined) {
|
||||
section.append(this._storyLinkifiedPre(ev.output));
|
||||
if (ev.outputTruncated) section.append(el('p', 'story-empty', 'Only the end of this output is kept.'));
|
||||
// A command that also wrote files (a here-document) offers the change, not only what it printed.
|
||||
if (ev.edits?.length) {
|
||||
const b = el(
|
||||
'button',
|
||||
'story-btn-s',
|
||||
`See the ${ev.edits.length === 1 ? 'file' : `${ev.edits.length} files`} it wrote`
|
||||
);
|
||||
b.type = 'button';
|
||||
b.dataset.storyOpen = `diff:event:${index}`;
|
||||
section.append(b);
|
||||
}
|
||||
} else if (ev.outputTruncated) {
|
||||
section.append(el('p', 'story-empty', 'This output was left out to keep a long turn fast.'));
|
||||
} else if (target && !target.startsWith('output:')) {
|
||||
|
||||
Reference in new issue
Block a user