Redesign Character sheets progress modal into a big split view (v1.14.16)
Was a small 420px dialog with a collapsible one-line JSON preview and a cramped 2-column chip grid. Now a big panel matching the audiobook casting live view's shape: passage text (left) and live JSON output (right) side by side, with a character sidebar on the right edge whose just-updated entries are highlighted — so the user can watch each sheet actually being built from its source passage instead of a tiny opaque progress bar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b96968ac5d
commit
b5ffa655d8
@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
|
||||
---
|
||||
|
||||
## [1.14.16] — 2026-07-09
|
||||
|
||||
### Changed
|
||||
- **Redesigned the Character sheets progress modal** — was a small (420px) dialog with a collapsible single-line JSON preview and a cramped chip grid. Now a large panel (like the audiobook casting live view): the passage currently being analysed on the left, the LLM's live JSON output filling in on the right, and a character sidebar on the right edge with the character(s) just updated highlighted — so you can watch each sheet actually being built from its source text.
|
||||
|
||||
---
|
||||
|
||||
## [1.14.15] — 2026-07-09
|
||||
|
||||
### Added
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="theme-color" content="#2563EB">
|
||||
<meta name="app-version" content="1.14.15">
|
||||
<meta name="app-version" content="1.14.16">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/static/icon.svg">
|
||||
@ -27,7 +27,7 @@
|
||||
|
||||
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
||||
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.14.15">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.14.16">
|
||||
|
||||
|
||||
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||
@ -365,7 +365,7 @@ window.toggleNavTree = function(treeId, chevronId) {
|
||||
</script>
|
||||
|
||||
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
||||
<script src="/static/loader.js?v=1.14.15"></script>
|
||||
<script src="/static/loader.js?v=1.14.16"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -314,7 +314,7 @@ async function csGenerate(text, cacheKey, initialRoster) {
|
||||
? `${knownTotal} cast characters queued · ${detailed} profiles with details`
|
||||
: `${map.size} characters found`;
|
||||
prog.update(i, `Passage ${i + 1} / ${chunks.length}…`, charLabel, csProgressSnapshot(map));
|
||||
prog.startPassage();
|
||||
prog.startPassage(chunks[i]);
|
||||
try {
|
||||
const body = { text: chunks[i], known_characters: roster.slice(0, 120), target_mode: targetMode, existing: csExistingSummary(map), language, llm_url, model };
|
||||
let data = null;
|
||||
@ -368,15 +368,32 @@ function csProgress(total) {
|
||||
if (!ov) {
|
||||
ov = document.createElement('div');
|
||||
ov.id = 'cs-progress'; ov.className = 'audiobook-overlay';
|
||||
ov.innerHTML = `<div class="audiobook-box cs-progress-box">
|
||||
// Big two-pane layout, mirroring the audiobook casting live view: the
|
||||
// passage being analysed on the left, the LLM's raw JSON output filling
|
||||
// in live on the right, and a character sidebar (current one highlighted)
|
||||
// so the user can watch each sheet actually being built.
|
||||
ov.innerHTML = `<div class="audiobook-box cs-progress-box cs-progress-box-big">
|
||||
<div class="cs-progress-head">
|
||||
<div class="audiobook-title"><span class="mdi mdi-account-details-outline"></span> Character sheets</div>
|
||||
<div class="audiobook-msg" id="cs-progress-msg">Analysing…</div>
|
||||
<div class="reader-synth-track" style="margin-bottom:4px"><div class="reader-synth-fill" id="cs-progress-fill"></div></div>
|
||||
<details class="cs-progress-live" id="cs-progress-live" open>
|
||||
<summary><span class="mdi mdi-text-long"></span> Live output <span class="cs-progress-live-status" id="cs-progress-live-status">watching…</span></summary>
|
||||
<div class="reader-synth-track" style="margin-bottom:0"><div class="reader-synth-fill" id="cs-progress-fill"></div></div>
|
||||
</div>
|
||||
<div class="cs-progress-body">
|
||||
<div class="cs-progress-main">
|
||||
<div class="cs-progress-col">
|
||||
<div class="cs-progress-col-label"><span class="mdi mdi-text-long"></span> Passage</div>
|
||||
<pre class="cs-progress-passage-pre" id="cs-progress-passage-pre"></pre>
|
||||
</div>
|
||||
<div class="cs-progress-col">
|
||||
<div class="cs-progress-col-label"><span class="mdi mdi-code-json"></span> Live output <span class="cs-progress-live-status" id="cs-progress-live-status">watching…</span></div>
|
||||
<pre class="cs-progress-live-pre" id="cs-progress-live-pre"></pre>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cs-progress-side">
|
||||
<div class="cs-progress-side-title" id="cs-progress-side-title">Characters found</div>
|
||||
<div class="cs-progress-chars" id="cs-progress-chars"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audiobook-actions"><button class="btn-secondary btn-sm" id="cs-progress-cancel">Cancel</button></div>
|
||||
</div>`;
|
||||
document.body.appendChild(ov);
|
||||
@ -386,13 +403,16 @@ function csProgress(total) {
|
||||
const fill = ov.querySelector('#cs-progress-fill');
|
||||
const msg = ov.querySelector('#cs-progress-msg');
|
||||
const chars = ov.querySelector('#cs-progress-chars');
|
||||
const sideTitle = ov.querySelector('#cs-progress-side-title');
|
||||
const passagePre = ov.querySelector('#cs-progress-passage-pre');
|
||||
const livePre = ov.querySelector('#cs-progress-live-pre');
|
||||
const liveStatus = ov.querySelector('#cs-progress-live-status');
|
||||
return {
|
||||
// Called once per passage before its request starts, so the live-output
|
||||
// pane is empty and honestly labelled before we know whether this model
|
||||
// streams real content or the request falls back to the blocking path.
|
||||
startPassage() {
|
||||
// Called once per passage before its request starts, so both panes are
|
||||
// reset and honestly labelled before we know whether this model streams
|
||||
// real content or the request falls back to the blocking path.
|
||||
startPassage(text) {
|
||||
if (passagePre) passagePre.textContent = text || '';
|
||||
if (livePre) livePre.textContent = '';
|
||||
if (liveStatus) { liveStatus.textContent = 'watching…'; liveStatus.className = 'cs-progress-live-status'; }
|
||||
},
|
||||
@ -411,12 +431,10 @@ function csProgress(total) {
|
||||
update(d, label, charLabel, roster = []) {
|
||||
if (fill) fill.style.width = (d / total * 100) + '%';
|
||||
if (msg && label) msg.textContent = label;
|
||||
if (sideTitle) sideTitle.textContent = charLabel || 'Characters found';
|
||||
if (chars) {
|
||||
const list = Array.isArray(roster) ? roster : [];
|
||||
chars.innerHTML = `
|
||||
<div class="cs-progress-summary">${escHtml(charLabel || '')}</div>
|
||||
${list.length ? `<div class="cs-progress-list">
|
||||
${list.map(c => `
|
||||
chars.innerHTML = list.length ? list.map(c => `
|
||||
<div class="cs-progress-chip ${c.status ? 'is-' + escHtml(c.status) : ''}">
|
||||
<span class="cs-progress-dot">${escHtml((c.name || '?')[0].toUpperCase())}</span>
|
||||
<span class="cs-progress-meta">
|
||||
@ -428,9 +446,7 @@ function csProgress(total) {
|
||||
</span>
|
||||
${c.status ? `<span class="cs-progress-badge">${escHtml(c.status)}</span>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>` : ''}
|
||||
`;
|
||||
`).join('') : '<div class="cs-progress-empty">reading…</div>';
|
||||
}
|
||||
},
|
||||
done() { ov.style.display = 'none'; },
|
||||
|
||||
@ -5780,32 +5780,42 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
}
|
||||
.cs-arc-note { font-size: 11px; color: var(--subtext); margin-top: 3px; font-style: italic; }
|
||||
.cs-progress-box { width: min(520px, 92vw); }
|
||||
.cs-progress-live { margin-bottom: 10px; border: 1px solid var(--border); border-radius: 8px; }
|
||||
.cs-progress-live summary {
|
||||
display: flex; align-items: center; gap: 6px; padding: 7px 10px; font-size: 11.5px;
|
||||
font-weight: 700; color: var(--subtext); cursor: pointer; list-style: none;
|
||||
/* Big layout: passage | live output side by side, character sidebar on the
|
||||
right (current/just-updated characters highlighted) — same shape as the
|
||||
audiobook casting live view, so you can watch each sheet being built. */
|
||||
.cs-progress-box-big { width: min(1400px, 96vw); height: min(880px, 90vh); display: flex; flex-direction: column; padding: 20px 22px; }
|
||||
.cs-progress-head { flex-shrink: 0; }
|
||||
.cs-progress-head .audiobook-msg { margin: 8px 0 10px; }
|
||||
.cs-progress-body { flex: 1; min-height: 0; display: flex; gap: 14px; margin-top: 12px; }
|
||||
.cs-progress-main { flex: 1; min-width: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
||||
.cs-progress-col { min-width: 0; min-height: 0; display: flex; flex-direction: column; gap: 6px; }
|
||||
.cs-progress-col-label {
|
||||
display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 800;
|
||||
text-transform: uppercase; letter-spacing: .04em; color: var(--subtext); flex-shrink: 0;
|
||||
}
|
||||
.cs-progress-live summary::-webkit-details-marker { display: none; }
|
||||
.cs-progress-live-status { margin-left: auto; font-weight: 500; font-style: italic; opacity: .8; }
|
||||
.cs-progress-live-status.is-live { color: var(--accent); opacity: 1; }
|
||||
.cs-progress-live-pre {
|
||||
margin: 0; padding: 8px 10px; max-height: 160px; overflow-y: auto; white-space: pre-wrap;
|
||||
overflow-wrap: anywhere; font-size: 11px; line-height: 1.5; color: var(--text);
|
||||
background: var(--bg); border-top: 1px solid var(--border);
|
||||
}
|
||||
.cs-progress-chars { font-size: 11px; color: var(--subtext); min-height: 16px; margin-bottom: 8px; }
|
||||
.cs-progress-summary { text-align: center; margin-bottom: 8px; color: var(--subtext); }
|
||||
.cs-progress-list {
|
||||
display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px;
|
||||
max-height: 210px; overflow: auto; padding: 2px 2px 4px;
|
||||
.cs-progress-passage-pre, .cs-progress-live-pre {
|
||||
flex: 1; min-height: 0; margin: 0; padding: 10px 12px; overflow-y: auto; white-space: pre-wrap;
|
||||
overflow-wrap: anywhere; font-size: 12px; line-height: 1.55; color: var(--text);
|
||||
background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
|
||||
}
|
||||
.cs-progress-live-pre { background: rgba(37,99,235,.05); }
|
||||
.cs-progress-side { width: 260px; flex-shrink: 0; display: flex; flex-direction: column; gap: 8px; min-height: 0; }
|
||||
.cs-progress-side-title { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; color: var(--subtext); flex-shrink: 0; }
|
||||
.cs-progress-chars { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; padding: 2px 2px 4px; }
|
||||
.cs-progress-empty { font-size: 12px; color: var(--subtext); font-style: italic; text-align: center; padding: 20px 0; }
|
||||
.cs-progress-chip {
|
||||
display: grid; grid-template-columns: 22px minmax(0, 1fr) 44px auto; align-items: center; gap: 6px;
|
||||
min-height: 34px; padding: 5px 6px; border: 1px solid var(--border); border-radius: 7px;
|
||||
background: var(--surface); color: var(--text);
|
||||
display: grid; grid-template-columns: 26px minmax(0, 1fr) 44px auto; align-items: center; gap: 8px;
|
||||
min-height: 38px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 8px;
|
||||
background: var(--surface); color: var(--text); flex-shrink: 0;
|
||||
}
|
||||
.cs-progress-chip.is-new { border-color: rgba(34,197,94,.45); background: rgba(34,197,94,.08); }
|
||||
.cs-progress-chip.is-updated { border-color: rgba(59,130,246,.45); background: rgba(59,130,246,.08); }
|
||||
.cs-progress-chip.is-updated { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); box-shadow: 0 0 0 1px var(--accent); }
|
||||
@media (max-width: 900px) {
|
||||
.cs-progress-box-big { width: 96vw; height: 92vh; }
|
||||
.cs-progress-body { flex-direction: column; }
|
||||
.cs-progress-main { grid-template-columns: 1fr; }
|
||||
.cs-progress-side { width: 100%; max-height: 180px; }
|
||||
}
|
||||
.cs-progress-dot {
|
||||
width: 22px; height: 22px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
|
||||
background: var(--accent); color: #fff; font-size: 10px; font-weight: 800;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user