runlist 0.96.0 → 0.97.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +170 -43
  2. package/assets/app/app.css +189 -3
  3. package/assets/app/app.mjs +49 -31
  4. package/assets/app/document-create.mjs +3 -2
  5. package/assets/app/document-lifecycle.mjs +17 -8
  6. package/assets/app/document-stages.mjs +3 -1
  7. package/assets/app/document-views.mjs +23 -1
  8. package/assets/app/editor-navigation.mjs +16 -23
  9. package/assets/app/filing-navigation.mjs +18 -16
  10. package/assets/app/git-changes.mjs +11 -2
  11. package/assets/app/index.html +34 -23
  12. package/assets/app/library-navigation.mjs +29 -16
  13. package/assets/app/record-navigation.mjs +68 -23
  14. package/assets/app/recovery-center.mjs +8 -7
  15. package/assets/app/settings.mjs +48 -10
  16. package/assets/app/template-editor.mjs +22 -12
  17. package/assets/app/transport.mjs +1 -2
  18. package/bin/runlist.mjs +64 -15
  19. package/package.json +1 -1
  20. package/plugins/runlist-codex/.codex-plugin/plugin.json +1 -1
  21. package/plugins/runlist-codex/skills/runlist/SKILL.md +3 -1
  22. package/src/app.mjs +9 -4
  23. package/src/baton.mjs +49 -11
  24. package/src/brief.mjs +145 -0
  25. package/src/commands.mjs +4 -3
  26. package/src/config.mjs +16 -26
  27. package/src/doctor.mjs +15 -0
  28. package/src/extractors.mjs +7 -14
  29. package/src/flags.mjs +5 -3
  30. package/src/hud.mjs +32 -63
  31. package/src/index.mjs +2 -0
  32. package/src/install.mjs +6 -4
  33. package/src/installation.mjs +117 -0
  34. package/src/new.mjs +27 -7
  35. package/src/prompt-handoff.mjs +132 -0
  36. package/src/prompts.mjs +112 -16
  37. package/src/section.mjs +46 -11
  38. package/src/show.mjs +132 -16
  39. package/src/template-store.mjs +16 -5
  40. package/src/update.mjs +7 -28
  41. package/src/util.mjs +7 -0
@@ -6,7 +6,7 @@ export function filingNavigation({ state, $, api, open, showLibrary, checkout, n
6
6
  const button = (label, action) => { const node = element('button', label, 'quiet small'); node.type = 'button'; node.onclick = run(action); return node; };
7
7
  const run = fn => async () => { try { await fn(); } catch (error) { notice(error.message, true); } };
8
8
  const heading = element('h1', 'Filing'); heading.id = 'filing-title'; heading.tabIndex = -1;
9
- const description = element('p', 'Where live plans are homed in status-bearing hub table rows. Links in prose are references. This report changes no files.', 'muted');
9
+ const description = element('p', 'Find plans missing a home or listed in more than one hub.', 'muted');
10
10
  const feedback = element('p'); feedback.id = 'filing-feedback'; feedback.setAttribute('role', 'status'); feedback.setAttribute('aria-live', 'polite');
11
11
  const totals = element('div', undefined, 'filing-totals'); totals.id = 'filing-totals';
12
12
  const controls = element('div', undefined, 'filing-controls');
@@ -14,7 +14,7 @@ export function filingNavigation({ state, $, api, open, showLibrary, checkout, n
14
14
  const search = element('input'); search.type = 'search'; search.id = 'filing-search'; search.placeholder = 'Title or path…'; searchLabel.append(search);
15
15
  const groupLabel = element('label', 'Show');
16
16
  const group = element('select'); group.id = 'filing-group';
17
- for (const [value, label] of [['all', 'All plans'], ['unfiled', 'Unfiled'], ['multiple', 'Multiple homes'], ['no-category', 'No category'], ['parent', 'Through parent'], ['direct', 'Direct home']]) {
17
+ for (const [value, label] of [['all', 'All plans'], ['unfiled', 'Unfiled'], ['multiple', 'Multiple homes'], ['no-category', 'No category'], ['parent', 'Via parent plan'], ['direct', 'Listed in a hub']]) {
18
18
  const option = element('option', label); option.value = value; group.append(option);
19
19
  }
20
20
  groupLabel.append(group);
@@ -29,26 +29,28 @@ export function filingNavigation({ state, $, api, open, showLibrary, checkout, n
29
29
  const previous = button('Previous', () => { offset = Math.max(0, offset - 50); return load(); }); previous.id = 'filing-previous';
30
30
  const next = button('Next', () => { offset += 50; return load(); }); next.id = 'filing-next';
31
31
  pager.append(previous, next);
32
- root.replaceChildren(heading, description, controls, feedback, totals, range, layout, pager);
32
+ const help = element('details'), helpTitle = element('summary', 'How homes are counted');
33
+ help.append(helpTitle, element('p', 'A home is a hub table row linking to a plan and showing its status. Links in prose are references. A plan can inherit its parent’s home. Finding groups can overlap. This report does not change files.', 'muted'));
34
+ root.replaceChildren(heading, description, controls, feedback, totals, range, layout, pager, help);
33
35
  const visible = () => state.mode === 'filing' && !root.hidden;
34
36
  const call = (op, params = {}) => api(`filing?${new URLSearchParams({ op, ...(scanId ? { scanId } : {}), ...params })}`);
35
- const label = plan => plan.filing === 'parent' ? `Through parent · ${plan.filedThrough}` : plan.filing === 'direct' ? 'Direct home' : plan.filing === 'unfiled' ? 'Unfiled' : plan.filing === 'observed' ? 'Observed home evidence · incomplete scan' : 'Home unknown · incomplete scan';
37
+ const label = plan => plan.filing === 'parent' ? `Via parent plan · ${plan.filedThrough}` : plan.filing === 'direct' ? 'Listed in a hub' : plan.filing === 'unfiled' ? 'Unfiled' : plan.filing === 'observed' ? 'Home found · scan incomplete' : 'Home unknown · incomplete scan';
36
38
  function deactivate() { sequence++; detailSequence++; clearTimeout(timer); root.hidden = true; $('library-filing').setAttribute('aria-pressed', 'false'); }
37
39
  function invalidate() {
38
40
  sequence++; detailSequence++; scanId = null;
39
- if (visible()) { feedback.textContent = 'Saved files changed. Refresh to review current filing evidence.'; list.setAttribute('aria-busy', 'false'); refresh.disabled = false; search.disabled = false; group.disabled = false; previous.disabled = true; next.disabled = true; }
41
+ if (visible()) { feedback.textContent = 'Files changed. Refresh to update this report.'; list.setAttribute('aria-busy', 'false'); refresh.disabled = false; search.disabled = false; group.disabled = false; previous.disabled = true; next.disabled = true; }
40
42
  }
41
43
  function renderSummary(result) {
42
44
  if (!result.enabled) feedback.textContent = result.reason;
43
- else if (result.state === 'scanning') feedback.textContent = `Reading filing evidence… ${result.progress.done} of ${result.progress.total} documents checked.`;
44
- else if (!result.complete) feedback.textContent = result.reason ?? 'Scan incomplete. Observed evidence is available; unfiled and unique-home findings are unavailable. Refresh to retry.';
45
- else feedback.textContent = `${result.totals.plans} live plans checked. Finding groups may overlap.`;
45
+ else if (result.state === 'scanning') feedback.textContent = `Checking plans… ${result.progress.done} of ${result.progress.total} documents checked.`;
46
+ else if (!result.complete) feedback.textContent = result.reason ?? 'Scan incomplete. Some homes may be missing; refresh to retry.';
47
+ else feedback.textContent = `${result.totals.plans} ${result.totals.plans === 1 ? 'plan' : 'plans'} checked.`;
46
48
  totals.replaceChildren();
47
- if (result.totals) for (const [key, title] of [['unfiled', 'Unfiled'], ['multiplyFiled', 'Multiple homes'], ['noCategory', 'No category'], ['filedThroughParent', 'Through parent']]) totals.append(element('span', `${title}: ${result.totals[key]}`));
49
+ if (result.totals) for (const [key, title] of [['unfiled', 'Unfiled'], ['multiplyFiled', 'Multiple homes'], ['noCategory', 'No category'], ['filedThroughParent', 'Via parent plan']]) totals.append(element('span', `${title}: ${result.totals[key]}`));
48
50
  if (result.failureCount) {
49
51
  const failures = element('details'), summary = element('summary', `${result.failureCount} unavailable sources or folders`); failures.append(summary);
50
52
  for (const failure of result.failures ?? []) failures.append(element('p', `${failure.path ?? 'Inventory'}: ${failure.message}`));
51
- if (result.failureCount > (result.failures?.length ?? 0)) failures.append(element('p', 'Additional failures omitted. Narrow the source problem and refresh.'));
53
+ if (result.failureCount > (result.failures?.length ?? 0)) failures.append(element('p', 'More sources failed. Fix unavailable sources and refresh.'));
52
54
  totals.append(failures);
53
55
  }
54
56
  for (const option of group.options) option.disabled = option.value !== 'all' && !result.complete;
@@ -65,7 +67,7 @@ export function filingNavigation({ state, $, api, open, showLibrary, checkout, n
65
67
  if (plan.noCategory) row.append(element('small', 'No category'));
66
68
  return row;
67
69
  }));
68
- if (!result.plans?.length) list.append(element('p', result.complete ? 'No plans match this filter.' : result.enabled ? 'No plan evidence is available in this scan.' : 'Enable filing in the repository configuration to use this report.', 'library-no-results'));
70
+ if (!result.plans?.length) list.append(element('p', result.complete ? 'No plans match this filter.' : result.enabled ? 'No plans available in this scan.' : 'Enable filing in the repository configuration to use this report.', 'library-no-results'));
69
71
  }
70
72
  async function inspect(path, homeOffset = 0) {
71
73
  if (!scanId || !visible()) return;
@@ -76,11 +78,11 @@ export function filingNavigation({ state, $, api, open, showLibrary, checkout, n
76
78
  const result = await call('detail', { path, homeOffset: String(homeOffset), homeLimit: '20' });
77
79
  if (own !== detailSequence || !visible()) return;
78
80
  const nodes = [element('h2', result.title), element('p', result.path, 'muted'), element('p', label(result)), button('Open plan', () => open(result.path))];
79
- if (result.filedThrough && result.filedThrough !== result.path) nodes.push(button('Open parent home', () => open(result.filedThrough)));
80
- if (!result.homes.length) nodes.push(element('p', result.complete ? 'No status-bearing hub subject row homes this plan.' : 'No home observed. Missing sources prevent an unfiled finding.'));
81
+ if (result.filedThrough && result.filedThrough !== result.path) nodes.push(button('Open parent plan', () => open(result.filedThrough)));
82
+ if (!result.homes.length) nodes.push(element('p', result.complete ? 'This plan is not listed in a hub.' : 'No home found yet. The scan is incomplete.'));
81
83
  for (const home of result.homes) {
82
84
  const row = element('article', undefined, 'filing-home-row');
83
- row.append(element('h3', `${home.hub}:${home.line}`), element('p', `Categories: ${home.categories.join(', ') || (result.complete ? 'No category' : 'No category observed · incomplete scan')}`), element('p', `Subject: ${home.subject} · Printed status: ${home.printedStatus}`));
85
+ row.append(element('h3', `${home.hub}:${home.line}`), element('p', `Categories: ${home.categories.join(', ') || (result.complete ? 'No category' : 'No category observed · incomplete scan')}`), element('p', `Plan: ${home.subject} · Status: ${home.printedStatus}`));
84
86
  if (home.categoriesShortened) row.append(element('p', 'Category display shortened. Open the hub for its full categories.', 'muted'));
85
87
  if (home.current === 'unchanged') row.append(button('Open hub row', () => open(home.hub, { line: home.line, expectedRevision: home.revision })));
86
88
  else { row.append(element('p', home.current === 'changed' ? 'Hub changed after this scan. Refresh before using this row location.' : 'Hub source is currently unavailable.', 'inline-note')); row.append(button('Open current hub', () => open(home.hub))); }
@@ -95,12 +97,12 @@ export function filingNavigation({ state, $, api, open, showLibrary, checkout, n
95
97
  }
96
98
  async function load({ refresh: force = false } = {}) {
97
99
  if (!visible()) return;
98
- if (!force && !scanId) { feedback.textContent = 'Refresh to review current filing evidence.'; return; }
100
+ if (!force && !scanId) { feedback.textContent = 'Refresh to update this report.'; return; }
99
101
  const own = ++sequence; list.setAttribute('aria-busy', 'true'); refresh.disabled = true;
100
102
  if (force) { search.disabled = true; group.disabled = true; previous.disabled = true; next.disabled = true; }
101
103
  try {
102
104
  if (force) {
103
- selected = null; detailSequence++; offset = 0; detail.replaceChildren(element('p', 'Select a plan to inspect its exact home evidence.', 'muted'));
105
+ selected = null; detailSequence++; offset = 0; detail.replaceChildren(element('p', 'Select a plan to see its hub links.', 'muted'));
104
106
  let result = await call('start', { refresh: '1' });
105
107
  if (own !== sequence || !visible()) return;
106
108
  scanId = result.scanId ?? null; renderSummary(result);
@@ -29,7 +29,15 @@ export function gitChanges({$,api,state,checkout,showLibrary,open,returnToDraft=
29
29
  button.innerHTML=`<strong>${esc(row.title??displayPath(row.path))}</strong><span class="changes-path">${esc(displayPath(row.path))}</span>${row.original?`<small>From ${esc(displayPath(row.original))}</small>`:''}<span class="changes-meta">${esc(row.kind)}${row.staged?' · Staged':''}${row.partial&&!row.filterReviewRequired?' · Partial staging':''}${row.filterReviewRequired?' · Clean-filter review required':''}</span>${row.blockedReason?`<small class="changes-blocked">${esc(row.blockedReason)}</small>`:''}`;
30
30
  button.onclick=run(()=>review(row.path));item.append(check,button);return item;
31
31
  }));
32
- if(data?.available&&!data.changes.length){const empty=document.createElement('p');empty.className='library-no-results';empty.textContent=$('changes-search').value?'No saved documents match this search.':'No saved document changes. Unsaved drafts stay in the editor.';$('changes-list').append(empty);}
32
+ if(data?.available&&!data.changes.length){const empty=document.createElement('p');empty.className='library-no-results';empty.textContent=$('changes-search').value?'No changed documents match this search.':'No saved document changes.';$('changes-list').append(empty);}
33
+ const hasRows=!!data?.changes?.length, available=!!data?.available;
34
+ root.classList.toggle('changes-empty',!hasRows);
35
+ $('changes-review').hidden=!hasRows;
36
+ root.querySelector('.changes-selection').hidden=!hasRows&&!selected.size;
37
+ root.querySelector('.library-pagination').hidden=!available||(!offset&&!data?.hasMore);
38
+ root.querySelector('.changes-footer').hidden=!hasRows;
39
+ root.querySelector('.changes-controls').hidden=!available||(!hasRows&&!$('changes-search').value&&!$('changes-archived').checked&&!data?.changedDocuments);
40
+ $('changes-checkout').hidden=!hasRows;
33
41
  $('changes-list').scrollTop=scroll;selection();
34
42
  if(activePath)[...$('changes-list').querySelectorAll('[data-git-path]')].find(node=>node.tagName===activeTag&&node.getAttribute('data-git-path')===activePath)?.focus();
35
43
  }
@@ -44,11 +52,12 @@ export function gitChanges({$,api,state,checkout,showLibrary,open,returnToDraft=
44
52
  if(old&&old.offset===result.offset&&old.query===params.get('q')&&old.archived===params.get('archived'))for(const row of old.changes)if(!result.changes.some(next=>next.path===row.path))selected.delete(row.path);
45
53
  data.query=params.get('q');data.archived=params.get('archived');
46
54
  $('changes-checkout').textContent=result.available?`${result.branch??'No branch'} · ${result.head?.slice(0,12)??'No commits yet'}`:'';
47
- $('changes-feedback').textContent=[result.reason??`${result.changedDocuments} changed documents · ${result.localOnly} local-only documents`,result.commitSupport?.reason].filter(Boolean).join(' ');
55
+ $('changes-feedback').textContent=!result.available?result.reason??'Git review is unavailable.':[result.reason,result.changes.length?`${result.changedDocuments} changed documents`:null,result.changes.length?result.commitSupport?.reason:null].filter(Boolean).join(' · ');
48
56
  $('changes-range').textContent=result.total?`${offset+1}–${offset+result.changes.length} of ${result.total}`:'0 documents';
49
57
  $('changes-previous').disabled=!offset||!result.available;$('changes-next').disabled=!result.hasMore||!result.available;
50
58
  $('library-changes-count').textContent=result.available?String(result.changedDocuments):'—';$('library-changes').title='Review saved document changes';
51
59
  $('changes-staging').textContent=result.unrelatedStaged?`${result.unrelatedStaged} staged files outside this document scope stay staged.`:'Selection and review leave Git staging unchanged.';
60
+ if(reviewed&&!result.changes.some(row=>row.path===reviewed)){reviewed=null;diffSequence++;$('changes-review').innerHTML='<h2>Select a file to review</h2>'; }
52
61
  rows();if(reviewed)await review(reviewed);
53
62
  }catch(error){if(own!==sequence||state.mode!=='changes')return;if(own===sequence&&state.mode==='changes')$('changes-feedback').textContent=`${error.message} Selection is kept. Try Refresh.`;throw error;}
54
63
  finally{if(own===sequence){$('changes-list').setAttribute('aria-busy','false');$('changes-refresh').disabled=false;}}
@@ -2,9 +2,17 @@
2
2
  <html lang="en">
3
3
  <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="light dark"><script src="/theme.mjs"></script><title>Runlist — Document library</title><link rel="stylesheet" href="/app.css"></head>
4
4
  <body>
5
- <header class="app-header"><button id="library-home" class="brand" aria-label="Library home"><span class="brand-mark">/</span>runlist</button><div class="checkout" id="checkout">Document library</div><div class="identity" id="identity"></div><label class="theme-control"><span aria-hidden="true">◐</span><span class="sr-only">Appearance</span><select id="theme-mode" aria-label="Appearance"><option value="system">System</option><option value="light">Light</option><option value="dark">Dark</option></select></label><button id="quick-open" class="quiet" hidden aria-keyshortcuts="Meta+K Control+K">Search <kbd>⌘ / Ctrl K</kbd></button><button id="new-document" class="primary" hidden>New…</button><button id="open-recovery" class="quiet" hidden>Recovery</button><button id="desktop-open" class="quiet" hidden>Open folder…</button><button id="disconnect" class="quiet" hidden>Disconnect</button></header>
5
+ <header class="app-header">
6
+ <button id="library-home" class="brand" aria-label="Library home"><span class="brand-mark">/</span>runlist</button>
7
+ <div class="checkout" id="checkout">Document library</div>
8
+ <div class="header-actions"><button id="quick-open" class="quiet" hidden aria-keyshortcuts="Meta+K Control+K">Search <kbd>⌘ / Ctrl K</kbd></button><button id="new-document" class="primary" hidden>New…</button>
9
+ <details id="workspace-menu" class="workspace-menu"><summary>Workspace</summary><div class="workspace-menu-content">
10
+ <div class="menu-heading">This checkout</div><div id="desktop-location" class="desktop-location" hidden></div><div class="identity" id="identity"></div>
11
+ <button id="open-recovery" class="quiet" hidden>Recovery</button><button id="desktop-open" class="quiet" hidden>Open folder…</button><button id="disconnect" class="quiet" hidden>Disconnect</button>
12
+ <label class="theme-control"><span>Appearance</span><select id="theme-mode" aria-label="Appearance"><option value="system">System</option><option value="light">Light</option><option value="dark">Dark</option></select></label>
13
+ </div></details></div>
14
+ </header>
6
15
  <div id="notice" role="status" aria-live="polite">Connecting to the local checkout…</div>
7
- <div id="desktop-location" class="desktop-location" hidden></div>
8
16
  <section id="connect-panel" class="connect-panel" hidden><h1>Open your checkout</h1><p>Use the access link printed by <code>runlist app</code>, or paste it here.</p><form id="connect-form"><label for="access">Local access link</label><input id="access" type="password" autocomplete="off" required><button class="primary">Connect</button></form></section>
9
17
  <section id="desktop-welcome" class="connect-panel desktop-welcome" hidden><span class="welcome-mark" aria-hidden="true">/</span><h1>Your work, in local files.</h1><p>Open a checkout to browse its hubs, plans, documents, decisions and flags.</p><button id="desktop-choose" class="primary">Open folder…</button><p class="muted">Local files · This computer only<br>Save to Markdown. Share through Git when you choose.</p><div id="desktop-recents" aria-label="Recent checkouts"></div></section>
10
18
  <div id="workspace" class="workspace library-start" hidden>
@@ -15,19 +23,22 @@
15
23
  <button id="library-all" class="quiet" aria-pressed="true"><span>All documents</span><span id="library-all-count"></span></button>
16
24
  <button id="library-hubs" class="quiet" aria-pressed="false"><span>Hubs</span><span id="library-hubs-count"></span></button>
17
25
  <button id="library-plans" class="quiet" aria-pressed="false"><span>Plans</span><span id="library-plans-count"></span></button>
18
- <button id="library-documents" class="quiet" aria-pressed="false"><span>Docs</span><span id="library-documents-count"></span></button>
26
+ <button id="library-documents" class="quiet" aria-pressed="false"><span>Documents</span><span id="library-documents-count"></span></button>
19
27
  </nav>
20
- <nav class="library-kinds record-kinds" aria-label="Records"><button id="library-flags" class="quiet" aria-pressed="false"><span>Flags</span><span id="library-flags-count"></span></button><button id="library-decisions" class="quiet" aria-pressed="false"><span>Decisions</span><span id="library-decisions-count"></span></button></nav>
21
- <nav class="library-kinds record-kinds" aria-label="Checkout"><button id="library-changes" class="quiet" aria-pressed="false"><span>Changes</span><span id="library-changes-count"></span></button><button id="library-filing" class="quiet" aria-pressed="false"><span>Filing</span><span id="library-filing-count"></span></button></nav>
22
- <div class="library-local"><section id="library-saved-section" hidden><details open><summary>PINNED</summary><nav id="library-saved" aria-label="Pinned documents"></nav></details></section><section id="library-recent-section" hidden><details open><summary>RECENT</summary><nav id="library-recent" aria-label="Recent documents"></nav></details></section></div>
23
- <div class="sidebar-bottom"><button id="open-templates" class="template-launch quiet">Templates <span>View &amp; customize</span></button><button id="open-settings" class="template-launch quiet">Settings <span>Preferences &amp; checkout</span></button><div class="sidebar-footer">Local Markdown<br><span>Save here. Review local commits in Changes.</span></div></div>
28
+ <div class="sidebar-group"><div class="sidebar-label">REVIEW &amp; ORGANIZE</div><nav class="library-kinds record-kinds" aria-label="Records"><button id="library-flags" class="quiet" aria-pressed="false"><span>Flags</span><span id="library-flags-count"></span></button><button id="library-decisions" class="quiet" aria-pressed="false"><span>Decisions</span><span id="library-decisions-count"></span></button></nav>
29
+ <nav class="library-kinds record-kinds" aria-label="Checkout"><button id="library-changes" class="quiet" aria-pressed="false"><span>Changes</span><span id="library-changes-count"></span></button><button id="library-filing" class="quiet" aria-pressed="false"><span>Filing</span><span id="library-filing-count"></span></button></nav></div>
30
+ <div class="library-local"><section id="library-saved-section" hidden><details open><summary>PINNED</summary><nav id="library-saved" aria-label="Pinned documents"></nav></details></section><section id="library-recent-section" hidden><details><summary>RECENT</summary><nav id="library-recent" aria-label="Recent documents"></nav></details></section></div>
31
+ <div class="sidebar-bottom"><button id="open-templates" class="template-launch quiet">Templates</button><button id="open-settings" class="template-launch quiet">Settings</button><div class="sidebar-footer"></div></div>
24
32
  </aside>
25
- <main id="main" aria-label="Checkout workspace"><div class="document-toolbar"><button id="toggle-plans" class="quiet" aria-expanded="false" aria-controls="sidebar">☰ Navigation</button><button id="back-library" class="quiet">← Library</button><div class="document-name"><div id="doc-title">Choose a document</div><span id="doc-path"></span></div><button id="pin-document" class="quiet document-pin" aria-label="Pin document" hidden>☆</button><span id="doc-status" class="status-badge"></span><button id="lifecycle-open" class="quiet small">Change status</button><div class="document-actions"><button id="undo" class="quiet" disabled>Undo save</button><button id="edit" class="primary" disabled>Review changes</button></div></div>
33
+ <main id="main" aria-label="Checkout workspace"><div class="document-toolbar"><div class="document-navigation"><button id="toggle-plans" class="quiet" aria-expanded="false" aria-controls="sidebar">☰ Navigation</button><button id="back-library" class="document-back">← Back to all documents</button></div><div class="document-heading"><div class="document-name"><div id="doc-title">Choose a document</div><span id="doc-path"></span></div><div class="document-controls"><button id="pin-document" class="quiet document-pin" aria-label="Pin document" title="Keep this document in Pinned" hidden>Pin</button><span id="doc-status" class="status-badge"></span><button id="lifecycle-open" class="quiet small">Change status</button><div class="document-actions"><button id="undo" class="quiet" disabled>Undo save</button><button id="edit" class="primary" disabled>Edit document</button></div></div></div></div>
26
34
  <section id="empty" class="library-home" aria-label="Browse documents">
27
35
  <div class="library-heading"><div><h1 id="library-title">All documents</h1><p id="library-description">Hubs, plans, and reference material across this checkout.</p></div><button id="library-refresh" class="quiet small" aria-label="Refresh library">↻ Refresh</button></div>
28
- <div class="library-controls"><label class="sr-only" for="filter">Find a document</label><input id="filter" class="search" placeholder="Search title, path, module…" type="search"><label class="sr-only" for="library-sort">Sort documents</label><select id="library-sort"><option value="updated">Recently updated</option><option value="relevance" hidden>Relevance · top matches</option><option value="title">Title</option><option value="path">File path</option></select><label class="content-search-choice"><input id="library-search-content" type="checkbox"> Search contents</label><label class="content-search-choice"><input id="library-search-semantic" type="checkbox"> Semantic · experimental</label><button id="library-semantic-search" class="quiet small" hidden>Search</button><button id="library-semantic-cancel" class="quiet small" hidden>Cancel</button><button id="save-library-view" class="quiet small">Save view…</button></div>
29
- <div class="saved-views"><label for="library-view">Saved views</label><select id="library-view"><option value="">Current filters</option></select><button id="delete-library-view" class="quiet small" hidden>Delete view</button></div>
30
- <details class="library-filters"><summary id="library-filter-label">Filters</summary><div class="filter-fields"><label>Status<select id="library-status"><option value="">All statuses</option></select></label><label id="library-stage-field" hidden>Stage<select id="library-stage"><option value="">All stages</option></select></label><label id="library-group-field" hidden>Group<select id="library-group"><option value="">No grouping</option><option value="stage">Stage</option></select></label><label>Document type<select id="library-type"><option value="">All types</option></select></label><label>Location<select id="library-folder"><option value="">All locations</option></select></label><label class="archive-choice"><input id="library-archived" type="checkbox"> Include archived</label><button id="library-clear" class="quiet small">Clear filters</button></div></details>
36
+ <div class="library-controls"><label class="sr-only" for="filter">Find a document</label><input id="filter" class="search" placeholder="Find a document…" type="search"><label class="sr-only" for="library-sort">Sort documents</label><select id="library-sort"><option value="updated">Recently updated</option><option value="relevance" hidden>Relevance · top matches</option><option value="title">Title</option><option value="path">File path</option></select><button id="library-semantic-search" class="quiet small" hidden>Search</button><button id="library-semantic-cancel" class="quiet small" hidden>Cancel</button></div>
37
+ <details id="library-filter-options" class="library-filters"><summary id="library-filter-label">Filters &amp; views</summary>
38
+ <div class="search-options"><span class="filter-section-label">Search in</span><label class="content-search-choice"><input id="library-search-content" type="checkbox"> Document contents</label><label class="content-search-choice"><input id="library-search-semantic" type="checkbox"> Semantic · experimental</label></div>
39
+ <div class="filter-fields"><label>Status<select id="library-status"><option value="">All statuses</option></select></label><label id="library-stage-field" hidden>Stage<select id="library-stage"><option value="">All stages</option></select></label><label id="library-group-field" hidden>Group<select id="library-group"><option value="">No grouping</option><option value="stage">Stage</option></select></label><label>Document type<select id="library-type"><option value="">All types</option></select></label><label>Location<select id="library-folder"><option value="">All locations</option></select></label><label class="archive-choice"><input id="library-archived" type="checkbox"> Include archived</label><button id="library-clear" class="quiet small">Clear filters</button></div>
40
+ <div class="saved-views"><label for="library-view">Saved views</label><select id="library-view"><option value="">Current filters</option></select><button id="save-library-view" class="quiet small">Save view…</button><button id="delete-library-view" class="quiet small" hidden>Delete view</button></div>
41
+ </details>
31
42
  <div class="library-pagination"><span id="library-range" role="status">Loading documents…</span><button id="library-previous" class="quiet small" aria-label="Previous document page" disabled>← Previous</button><button id="library-next" class="quiet small" aria-label="Next document page" disabled>Next →</button></div>
32
43
  <div class="library-columns" aria-hidden="true"><span>Document</span><span>Type</span><span>Status</span><span>Updated</span></div>
33
44
  <nav id="plans" aria-label="Document library"></nav><p id="library-scan-note" class="muted"></p>
@@ -35,33 +46,33 @@
35
46
  <section id="records-home" class="library-home records-home" hidden aria-label="Browse records"></section>
36
47
  <section id="filing-home" class="library-home filing-home" hidden aria-label="Plan filing"></section>
37
48
  <section id="changes-home" class="library-home changes-home" hidden aria-labelledby="changes-title">
38
- <div class="library-heading"><div><h1 id="changes-title" tabindex="-1">Changes</h1><p>Review saved documents against local Git history.</p></div><button id="changes-refresh" class="quiet small">↻ Refresh</button></div>
49
+ <div class="library-heading"><div><h1 id="changes-title" tabindex="-1">Changes</h1></div><button id="changes-refresh" class="quiet small">↻ Refresh</button></div>
39
50
  <p id="changes-checkout" class="muted"></p><p id="changes-feedback" role="status" aria-live="polite"></p>
40
- <div id="changes-draft" class="inline-note" hidden><span>Draft—not saved yet. Review saved Markdown here, or return to the document to save your draft.</span><button id="changes-return-draft" class="quiet small">Return to draft</button></div>
51
+ <div id="changes-draft" class="inline-note" hidden><span>You have an unsaved draft.</span><button id="changes-return-draft" class="quiet small">Return to draft</button></div>
41
52
  <div class="changes-controls"><label class="sr-only" for="changes-search">Find a changed document</label><input id="changes-search" class="search" type="search" placeholder="Find a changed document…"><label class="archive-choice"><input id="changes-archived" type="checkbox"> Include archived</label></div>
42
53
  <div class="library-pagination"><span id="changes-range"></span><button id="changes-previous" class="quiet small" disabled>← Previous</button><button id="changes-next" class="quiet small" disabled>Next →</button></div>
43
- <div class="changes-layout"><section class="changes-files" aria-label="Saved file selection"><div class="changes-selection"><span id="changes-selected">0 selected</span><button id="changes-clear" class="quiet small" disabled>Clear selection</button><button id="changes-commit" class="primary small" disabled>Review local commit</button></div><div id="changes-list" aria-busy="false"></div></section><section id="changes-review" class="changes-review" aria-label="Saved file diff"><h2>Select a file to review</h2><p class="muted">Select whole saved files. Selection leaves Git staging unchanged.</p></section></div>
44
- <div class="changes-footer"><p id="changes-staging" class="muted"></p><p class="muted">Commit selected saved files locally. Sharing remains in your Git workflow.</p></div>
54
+ <div class="changes-layout"><section class="changes-files" aria-label="Saved file selection"><div class="changes-selection"><span id="changes-selected">0 selected</span><button id="changes-clear" class="quiet small" disabled>Clear selection</button><button id="changes-commit" class="primary small" disabled>Review local commit</button></div><div id="changes-list" aria-busy="false"></div></section><section id="changes-review" class="changes-review" aria-label="Saved file diff"><h2>Select a file to review</h2></section></div>
55
+ <div class="changes-footer"><p id="changes-staging" class="muted"></p></div>
45
56
  </section>
46
57
  <section id="document" hidden aria-labelledby="doc-title">
47
58
  <section id="yardstick-panel" class="yardstick-panel" hidden aria-label="Product goal"></section>
48
- <div class="view-tabs"><div id="document-tabs" class="document-view-tabs" role="tablist" aria-label="Document view"><button role="tab" id="tab-read" aria-controls="reading" tabindex="-1" aria-selected="false">Preview</button><button role="tab" id="tab-edit" aria-controls="editing" tabindex="0" aria-selected="true">Edit</button><button role="tab" id="tab-review" aria-controls="reviewing" tabindex="-1" aria-selected="false">Review changes <span id="change-count"></span></button><button role="tab" id="tab-source" aria-controls="source-view" tabindex="-1" aria-selected="false">Source</button></div><button id="toggle-outline" class="quiet small">Sections</button><span id="draft-status" class="draft-status"></span></div>
59
+ <div class="view-tabs"><div id="document-tabs" class="document-view-tabs" role="tablist" aria-label="Document view"><button role="tab" id="tab-read" aria-controls="reading" tabindex="0" aria-selected="true">Preview</button><button role="tab" id="tab-edit" aria-controls="editing" tabindex="-1" aria-selected="false">Edit</button><button role="tab" id="tab-review" aria-controls="reviewing" tabindex="-1" aria-selected="false">Review changes <span id="change-count"></span></button><button role="tab" id="tab-source" aria-controls="source-view" tabindex="-1" aria-selected="false">Source</button></div><button id="toggle-outline" class="quiet small" aria-expanded="false" aria-controls="evidence" aria-keyshortcuts="Meta+Shift+J Control+Shift+J" title="Search sections (⌘ / Ctrl + Shift + J)">Details</button><span id="draft-status" class="draft-status"></span></div>
49
60
  <div id="claim" class="inline-note" hidden></div>
50
- <div id="pending-note" class="inline-note recovery" hidden><span>An interrupted operation is kept. Retry its reviewed request or inspect the outcome before editing further.</span><button id="inspect-pending" class="small">Inspect operation</button></div>
61
+ <div id="pending-note" class="inline-note recovery" hidden><span>Save interrupted. Inspect before retrying.</span><button id="inspect-pending" class="small">Inspect operation</button></div>
51
62
  <div id="recovery" class="inline-note recovery" hidden><span id="recovery-label"></span><button id="recover" class="small">Recover draft</button><button id="discard-recovery" class="quiet small">Discard draft</button></div>
52
63
  <div id="conflict" class="conflict" hidden><h2>The file changed while you were editing</h2><p>Your draft is kept. Compare the changes below, edit your draft to include what you want to keep, then review it against the current file.</p><div class="conflict-columns"><section><h3>Changes in the file</h3><div id="current-diff" class="diff"></div><details><summary>Current source</summary><pre id="current-source"></pre></details></section><section><h3>Your draft changes</h3><div id="draft-diff" class="diff"></div><details><summary>Original base source</summary><pre id="base-source"></pre></details></section></div><button id="review-merge" class="primary">Review merged draft</button><button id="use-current" class="quiet">Keep draft and read current file</button></div>
53
64
  <div id="operation-repair" class="inline-note" hidden><p id="repair-label"></p><details><summary>Retained operation evidence</summary><pre id="repair-evidence"></pre></details><button id="settle-operation" class="small">Leave current file and settle operation</button></div>
54
65
  <article id="reading" role="tabpanel" aria-labelledby="tab-read" tabindex="0" class="markdown"></article>
55
- <section id="editing" role="tabpanel" aria-labelledby="tab-edit" tabindex="0" class="editing" hidden><div id="document-stage-fields" class="document-stage-fields" hidden><label for="document-stage">Stage</label><select id="document-stage"><option value="">Unset · no stage</option></select><p id="document-stage-meaning" class="muted"></p></div><div class="inline-editor-hint"><span>Click anywhere to write. Type <kbd>/</kbd> for blocks.</span><button id="toggle-markdown" class="quiet small" aria-expanded="false">Edit Markdown</button></div><div id="block-editor" class="markdown"></div><div id="markdown-fallback" hidden><div class="editor-caption"><span>Markdown body</span><span>Metadata and lifecycle history are preserved.</span></div><label class="sr-only" for="body-editor">Markdown body</label><textarea id="body-editor" spellcheck="false"></textarea></div><div class="editor-footer"><button id="review" class="primary">Review changes</button><button id="cancel" class="quiet">Preview</button><button id="discard" class="quiet">Discard changes</button></div></section>
56
- <section id="reviewing" role="tabpanel" aria-labelledby="tab-review" tabindex="0" class="reviewing" hidden><div class="review-heading"><h2>Source changes</h2><p id="review-summary"></p></div><div id="diff" class="diff"></div><div class="save-bar"><div><strong>Save to this checkout</strong><p>Writes this Markdown file. Git commit and sharing are separate.</p></div><button id="back-edit" class="quiet">Back to edit</button><button id="document-changes" class="quiet">Saved Git changes</button><button id="save" class="primary" disabled>Save changes</button></div></section>
57
- <section id="source-view" role="tabpanel" aria-labelledby="tab-source" tabindex="0" class="source-view" hidden><div class="editor-caption"><span>Canonical source · read only</span><button id="copy-source" class="quiet small">Copy source</button></div><pre id="full-source"></pre></section>
66
+ <section id="editing" role="tabpanel" aria-labelledby="tab-edit" tabindex="0" class="editing" hidden><details id="document-stage-fields" class="document-stage-fields" hidden><summary>Plan stage</summary><div class="stage-controls"><label for="document-stage">Stage</label><select id="document-stage"><option value="">Unset · no stage</option></select><p id="document-stage-meaning" class="muted"></p></div></details><div id="block-editor" class="markdown"></div><div id="markdown-fallback" hidden><div class="editor-caption"><span>Markdown body</span></div><label class="sr-only" for="body-editor">Markdown body</label><textarea id="body-editor" spellcheck="false"></textarea></div><div id="document-edit-actions" class="editor-footer" hidden><button id="review" class="primary">Review changes</button><button id="cancel" class="quiet">Preview</button><button id="discard" class="quiet">Discard changes</button></div></section>
67
+ <section id="reviewing" role="tabpanel" aria-labelledby="tab-review" tabindex="0" class="reviewing" hidden><div class="review-heading"><h2>Source changes</h2><p id="review-summary"></p></div><div id="diff" class="diff"></div><div class="save-bar"><button id="back-edit" class="quiet">Back to edit</button><button id="document-changes" class="quiet">Saved Git changes</button><button id="save" class="primary" disabled>Save changes</button></div></section>
68
+ <section id="source-view" role="tabpanel" aria-labelledby="tab-source" tabindex="0" class="source-view" hidden><div class="editor-caption"><span>Source · read only</span><button id="copy-source" class="quiet small">Copy source</button></div><pre id="full-source"></pre></section>
58
69
  </section></main>
59
- <aside id="evidence" class="evidence"><section class="section-navigation" aria-label="Document sections"><div class="sidebar-label">ON THIS PAGE <span id="section-count"></span><button id="close-outline" class="quiet small" aria-label="Close sections">×</button></div><label class="sr-only" for="section-filter">Find a section</label><input id="section-filter" class="search" type="search" placeholder="Jump to a section…"><nav id="outline" aria-label="Section outline"></nav><button id="add-section" class="quiet small">+ Add section</button><div class="sidebar-label bookmark-heading">BOOKMARKS</div><nav id="bookmarks" aria-label="Bookmarked sections"></nav><p id="bookmark-empty" class="muted">Star a heading to keep it here.<br>Bookmarks stay in this browser.</p><p class="navigation-keys">⌘ / Ctrl + Shift + J to jump<br>Alt + Page Up / Down for sections</p></section><details class="file-evidence" open><summary>FILE &amp; EVIDENCE</summary><section id="document-connections" class="document-connections" hidden></section><div id="file-info" class="file-info"><p class="muted">Select a document to inspect its file and related flags.</p></div><div id="flags"></div></details></aside>
70
+ <aside id="evidence" class="evidence"><section class="section-navigation" aria-label="Document sections"><div class="sidebar-label">SECTIONS <span id="section-count"></span><button id="close-outline" class="quiet small" aria-label="Close details">×</button></div><label class="sr-only" for="section-filter">Find a section</label><input id="section-filter" class="search" type="search" placeholder="Jump to a section…"><nav id="outline" aria-label="Section outline"></nav><button id="add-section" class="quiet small">+ Add section</button></section><details class="file-evidence"><summary>FILE &amp; EVIDENCE</summary><section id="document-connections" class="document-connections" hidden></section><div id="file-info" class="file-info"><p class="muted">Select a document to inspect its file and related flags.</p></div><div id="flags"></div><button id="toggle-markdown" class="quiet small" aria-expanded="false">Edit Markdown</button></details></aside>
60
71
  </div>
61
- <dialog aria-labelledby="template-dialog-title" id="template-dialog" class="template-dialog"><div class="template-header"><div><h2 id="template-dialog-title">Templates</h2><p>Scaffolds for new documents in this checkout.</p></div><button id="close-templates" class="quiet" aria-label="Close templates">×</button></div><div class="template-layout"><nav id="template-list" aria-label="Templates"></nav><section class="template-detail"><div class="template-heading"><h3 id="template-name"></h3><span id="template-origin" class="muted"></span></div><p id="template-description"></p><p id="template-config-note" class="inline-note" hidden>This repo defines a JavaScript template. Its source is shown below. Saving Markdown creates an override for this type.</p><details id="template-config" hidden><summary>Repository JavaScript source</summary><pre id="template-config-source"></pre></details><div class="template-tabs"><button id="template-edit-tab" class="quiet" aria-pressed="true">Edit Markdown</button><button id="template-preview-tab" class="quiet" aria-pressed="false">Preview example</button></div><label class="sr-only" for="template-source">Template Markdown</label><textarea id="template-source" spellcheck="false"></textarea><article id="template-preview" class="markdown" hidden></article><p id="template-tokens" class="muted">Placeholders: <code>{{title}}</code>, <code>{{status}}</code>, <code>{{date}}</code>, <code>{{body}}</code>, <code>{{version}}</code>. Keep the metadata fields and body placeholder.</p><button id="template-reload" class="quiet small" hidden>Reload saved templates, keep draft</button><div id="template-feedback" role="status" aria-live="polite"></div><details id="template-review" hidden><summary>Changes to the saved template</summary><div id="template-diff" class="diff"></div></details><div class="template-actions"><button id="template-review-button" class="primary">Review template changes</button><button id="template-save" class="primary" hidden>Save template</button><button id="template-reset" class="quiet">Use original template</button><button id="template-discard" class="quiet">Discard draft</button></div><p class="muted">Save writes <code>runlist.templates.json</code> for future <code>runlist new</code> commands. Existing documents keep their content.</p></section></div></dialog>
72
+ <dialog aria-labelledby="template-dialog-title" id="template-dialog" class="template-dialog"><div class="template-header"><div><h2 id="template-dialog-title">Templates</h2></div><button id="close-templates" class="quiet" aria-label="Close templates">×</button></div><div class="template-layout"><nav id="template-list" aria-label="Templates"></nav><section class="template-detail"><div class="template-heading"><h3 id="template-name"></h3><span id="template-origin" class="muted"></span></div><p id="template-description"></p><p id="template-config-note" class="inline-note" hidden>This repo defines a JavaScript template. Its source is shown below. Saving Markdown creates an override for this type.</p><details id="template-config" hidden><summary id="template-config-label">Repository JavaScript source</summary><pre id="template-config-source"></pre></details><div class="template-tabs"><button id="template-edit-tab" class="quiet" aria-pressed="true">Edit Markdown</button><button id="template-preview-tab" class="quiet" aria-pressed="false">Preview example</button></div><label class="sr-only" for="template-source">Template Markdown</label><textarea id="template-source" spellcheck="false"></textarea><article id="template-preview" class="markdown" hidden></article><details class="template-help"><summary>Placeholders and saving</summary><p id="template-tokens" class="muted">Placeholders: <code>{{title}}</code>, <code>{{status}}</code>, <code>{{date}}</code>, <code>{{body}}</code>, <code>{{version}}</code>. Keep the metadata fields and body placeholder.</p><p id="template-save-note" class="muted"></p></details><button id="template-reload" class="quiet small" hidden>Reload saved templates, keep draft</button><div id="template-feedback" role="status" aria-live="polite"></div><details id="template-review" hidden><summary>Changes to the saved template</summary><div id="template-diff" class="diff"></div></details><div class="template-actions"><button id="template-review-button" class="primary">Review template changes</button><button id="template-save" class="primary" hidden>Save template</button><button id="template-reset" class="quiet">Use original template</button><button id="template-discard" class="quiet">Discard draft</button></div></section></div></dialog>
62
73
  <dialog aria-labelledby="settings-dialog-title" id="settings-dialog" class="settings-dialog"><div class="template-header"><div><h2 id="settings-dialog-title">Settings</h2><p id="settings-checkout"></p></div><button id="settings-close" class="quiet" aria-label="Close settings">×</button></div><div id="settings-content"></div></dialog>
63
74
  <dialog aria-labelledby="lifecycle-dialog-title" id="lifecycle-dialog" class="lifecycle-dialog"><div class="template-header"><div><h2 id="lifecycle-dialog-title">Change document status</h2><p id="lifecycle-path"></p></div><button id="lifecycle-close" class="quiet" aria-label="Close status change">×</button></div><div id="lifecycle-content"></div></dialog>
64
- <dialog id="create-dialog" class="create-dialog" aria-labelledby="create-title"><div class="template-header"><div><h2 id="create-title">New document</h2><p>Create a Markdown file in this checkout.</p></div><button id="create-close" class="quiet" aria-label="Close new document">×</button></div><form id="create-form"><div class="create-fields"><label>Template<select id="create-template"></select></label><label>Initial status<select id="create-status"></select></label><label class="field-wide"><span id="create-name-label">Title</span><input id="create-name" required maxlength="200" placeholder="What is this document about?"></label><label>Folder<input id="create-folder" required list="create-folders"></label><datalist id="create-folders"></datalist><label>Filename<input id="create-filename" required pattern="[a-z0-9][a-z0-9_-]{0,119}\.md" placeholder="document-name.md"></label><label class="field-wide">Initial content <span class="muted">optional</span><textarea id="create-body" rows="5" maxlength="262144" placeholder="Start writing, or leave the template scaffold to fill in later."></textarea></label><fieldset id="create-flag-fields" class="field-wide" hidden><legend>Finding severity</legend><label>Severity<select id="create-severity"><option value="warn">Warning</option><option value="problem">Problem</option><option value="info">Information</option></select></label><p class="muted">Starts open and unreviewed. Triage and resolution are separate actions.</p></fieldset><fieldset id="create-decision-fields" class="field-wide" hidden><legend>Alternatives</legend><p class="muted">Describe at least two approaches. Add consequences where they help the decision.</p><div id="create-options"></div><button id="create-option-add" type="button" class="quiet small">+ Add alternative</button><p class="muted">Starts open. Creating this question does not select an answer.</p></fieldset></div><p id="create-origin" class="muted"></p><button id="create-preview-button" class="primary">Preview document</button></form><section id="create-review" hidden><div class="review-heading"><h3 id="create-path"></h3><p>Confirm the destination and content before creating the file.</p></div><article id="create-preview" class="markdown"></article><details><summary>Markdown source</summary><pre id="create-source"></pre></details><section id="create-setup" hidden><h3>Repository record settings</h3><p>This first record also creates <code>runlist.records.json</code> to keep record identities and their folder consistent across checkouts. Review both files before creating.</p><pre id="create-setup-source"></pre></section><div class="dialog-actions"><button id="create-back" class="quiet">Back to form</button><button id="create-inspect" class="quiet">Inspect outcome</button><button id="create-confirm" class="primary">Create document</button></div></section><p id="create-feedback" role="status" aria-live="polite"></p><ul id="create-diagnostics"></ul><button id="create-discard" class="quiet small">Discard this new document draft</button><p class="muted">Creates local Markdown. Git commit and sharing remain separate.</p></dialog>
75
+ <dialog id="create-dialog" class="create-dialog" aria-labelledby="create-title"><div class="template-header"><div><h2 id="create-title">New document</h2></div><button id="create-close" class="quiet" aria-label="Close new document">×</button></div><form id="create-form"><div class="create-fields"><label>Template<select id="create-template"></select></label><label class="field-wide"><span id="create-name-label">Title</span><input id="create-name" required maxlength="200" placeholder="What is this document about?"></label><label class="field-wide">Content (optional)<textarea id="create-body" rows="5" maxlength="262144" placeholder="Start writing…"></textarea></label><fieldset id="create-flag-fields" class="field-wide" hidden><legend>Finding severity</legend><label>Severity<select id="create-severity"><option value="warn">Warning</option><option value="problem">Problem</option><option value="info">Information</option></select></label><p class="muted">Starts open and unreviewed. Triage and resolution are separate actions.</p></fieldset><fieldset id="create-decision-fields" class="field-wide" hidden><legend>Alternatives</legend><p class="muted">Describe at least two approaches. Add consequences where they help the decision.</p><div id="create-options"></div><button id="create-option-add" type="button" class="quiet small">+ Add alternative</button><p class="muted">Starts open. Creating this question does not select an answer.</p></fieldset><details id="create-file-options" class="field-wide"><summary>File options</summary><div class="create-fields"><label>Initial status<select id="create-status"></select></label><label>Folder<input id="create-folder" required list="create-folders"></label><datalist id="create-folders"></datalist><label>Filename<input id="create-filename" required pattern="[a-z0-9][a-z0-9_\-]{0,119}\.md" placeholder="document-name.md"></label></div><p id="create-origin" class="muted"></p></details></div><button id="create-preview-button" class="primary">Preview document</button></form><section id="create-review" hidden><div class="review-heading"><h3 id="create-path"></h3></div><article id="create-preview" class="markdown"></article><details><summary>Markdown source</summary><pre id="create-source"></pre></details><section id="create-setup" hidden><h3>Repository record settings</h3><p>This first record also creates <code>runlist.records.json</code> to keep record identities and their folder consistent across checkouts. Review both files before creating.</p><pre id="create-setup-source"></pre></section><div class="dialog-actions"><button id="create-back" class="quiet">Back to form</button><button id="create-inspect" class="quiet">Inspect outcome</button><button id="create-confirm" class="primary">Create document</button></div></section><p id="create-feedback" role="status" aria-live="polite"></p><ul id="create-diagnostics"></ul><button id="create-discard" class="quiet small">Discard draft</button></dialog>
65
76
  <dialog id="quick-dialog" class="quick-dialog" aria-labelledby="quick-title"><div class="template-header"><h2 id="quick-title">Go to…</h2><button id="quick-close" class="quiet" aria-label="Close search">×</button></div><label class="sr-only" for="quick-query">Find documents, sections or actions</label><input id="quick-query" class="search" type="search" autocomplete="off" placeholder="Find documents, sections or actions…" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="quick-results"><div class="quick-controls"><label>Search mode <select id="quick-mode"><option value="text">Text</option><option value="semantic">Semantic · experimental</option></select></label><button id="quick-search" class="primary" hidden>Search</button><button id="quick-cancel" class="quiet" hidden>Cancel</button><button id="quick-tool-settings" class="quiet" hidden>Tool settings</button></div><label class="archive-choice"><input id="quick-archived" type="checkbox"> Include archived</label><p id="quick-status" role="status" aria-live="polite"></p><div id="quick-results" role="listbox" aria-label="Search results"></div><p id="quick-hint" class="muted">↑ ↓ to choose · Enter to open · Esc to close. Searches local document contents on demand.</p></dialog>
66
77
  <dialog id="recovery-dialog" class="recovery-dialog" aria-labelledby="recovery-title"><div class="template-header"><div><h2 id="recovery-title">Recovery</h2><p>Drafts and unfinished operations in this checkout.</p></div><button id="recovery-close" class="quiet" aria-label="Close recovery">×</button></div><div class="recovery-controls"><label class="sr-only" for="recovery-filter">Filter recovery items</label><input id="recovery-filter" class="search" type="search" placeholder="Filter by document or state…"><button id="recovery-refresh" class="quiet">Refresh</button></div><p class="inline-note">Draft preserved locally means the Markdown file has not been saved. Interrupted operations require inspection; opening this view never retries a write.</p><p id="recovery-status" role="status" aria-live="polite"></p><div id="recovery-items"></div><button id="recovery-more" class="quiet" hidden>Load more</button></dialog>
67
78
  <dialog id="filter-dialog" class="filter-dialog" aria-labelledby="filter-title"><div class="template-header"><h2 id="filter-title">Save library view</h2><button id="filter-close" class="quiet" aria-label="Close saved view">×</button></div><form id="filter-form"><label>View name<input id="filter-name" required maxlength="60" placeholder="For example: Active release plans"></label><p class="muted">Remembers the current search, category, filters, sort, grouping and archived choice on this computer.</p><button class="primary">Save view</button><p id="filter-feedback" role="status"></p></form></dialog>
@@ -15,7 +15,8 @@ export function libraryNavigation({state,$,api,open,showLibrary,checkout,notice}
15
15
  $('library-view').value=id;viewOptions();await load({reset:true});
16
16
  }
17
17
  const storageKey=()=>`runlist:library:${checkout()}`;
18
- function keep(){try{localStorage.setItem(storageKey(),JSON.stringify({recent,favorites}));}catch{notice('Recent documents and pins could not be saved in local editor storage.',true);}}
18
+ function keep(){try{localStorage.setItem(storageKey(),JSON.stringify({recent,favorites}));return true;}catch{notice('Could not save pinned and recent documents. Try again.',true);return false;}}
19
+ function setFavorites(next){const previous=favorites;favorites=next;if(!keep())favorites=previous;else notice(next.length>previous.length?'Document pinned.':'Document unpinned.');renderLocal();}
19
20
  function button(doc,compact=false){
20
21
  const button=document.createElement('button'),active=!['home','records'].includes(state.mode)&&state.doc?.path===doc.path;
21
22
  button.className=`plan${active?' active':''}`;button.title=doc.path;
@@ -32,39 +33,51 @@ export function libraryNavigation({state,$,api,open,showLibrary,checkout,notice}
32
33
  status.append(dot,document.createTextNode(doc.status??'unknown'));
33
34
  const updated=document.createElement('span');updated.className='plan-updated';const date=new Date(/^\d{4}-\d{2}-\d{2}$/.test(doc.updated)?`${doc.updated}T12:00:00`:doc.updated??NaN);
34
35
  updated.textContent=Number.isNaN(date.getTime())?'—':date.toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'});
35
- if(doc.kind==='plan'){const stage=document.createElement('small');stage.className='plan-stage';stage.textContent=`Stage · ${doc.stageInvalid?'Invalid metadata':doc.stage??'Unset · no stage'}`;label.append(stage);}
36
+ if(doc.kind==='plan'&&(doc.stage||doc.stageInvalid)){const stage=document.createElement('small');stage.className='plan-stage';stage.textContent=`Stage · ${doc.stageInvalid?'Invalid metadata':doc.stage??'Unset · no stage'}`;label.append(stage);}
36
37
  button.append(label,type,status,updated);
37
38
  }
38
39
  button.onclick=async()=>{try{await open(doc.path);}catch(error){notice(error.message,true);}};return button;
39
40
  }
41
+ function clearResults(message,{error,retry}={}){
42
+ result=null;state.plans=[];$('plans').replaceChildren();$('library-scan-note').textContent='';
43
+ const empty=document.createElement('p');empty.className='library-no-results';empty.textContent=error?`${message}: ${error.message||'Please try again.'}`:message;empty.setAttribute('role',error?'alert':'status');$('plans').append(empty);
44
+ if(retry){const button=document.createElement('button');button.className='quiet';button.id='library-retry';button.textContent='Retry';button.onclick=retry;$('plans').append(button);}
45
+ $('library-range').textContent=message;$('library-previous').disabled=true;$('library-next').disabled=true;
46
+ }
47
+ function renderLocal(){
48
+ const kind=$('library-kind').value||'all',destination=$('filter').value.trim()?'search results':({all:'all documents',hubs:'hubs',plans:'plans',documents:'documents'})[kind]??'all documents';
49
+ $('back-library').textContent=`← Back to ${destination}`;$('back-library').title=`Return to ${destination}; your filters and drafts are kept`;
50
+ $('library-saved').replaceChildren(...favorites.map(doc=>{const row=document.createElement('div');row.className='saved-document';const remove=document.createElement('button');remove.className='quiet small';remove.textContent='×';remove.setAttribute('aria-label',`Unpin ${doc.title}`);remove.onclick=()=>{setFavorites(favorites.filter(f=>f.path!==doc.path));};row.append(button(doc,true),remove);return row;}));$('library-recent').replaceChildren(...recent.filter(r=>!favorites.some(f=>f.path===r.path)).slice(0,6).map(doc=>button(doc,true)));$('library-saved-section').hidden=!favorites.length;$('library-recent-section').hidden=!recent.length;
51
+ $('pin-document').hidden=!state.doc;const pinned=!!state.doc&&favorites.some(r=>r.path===state.doc.path);$('pin-document').textContent=pinned?'Pinned':'Pin';$('pin-document').title=pinned?'Remove from Pinned':'Keep this document in Pinned';$('pin-document').setAttribute('aria-label',pinned?'Unpin document':'Pin document');$('pin-document').setAttribute('aria-pressed',String(pinned));
52
+ }
40
53
  function render(){
54
+ renderLocal();
41
55
  if(!result)return;
42
56
  const entries=[];let previous;for(const doc of state.plans){const key=doc.stageInvalid?'@invalid':doc.stage===null?'@unset':`word:${doc.stage}`;if(result.group==='stage'&&key!==previous){const section=document.createElement('div');section.className='library-stage-heading';const heading=document.createElement('h2'),definition=result.stageDefinitions?.find(s=>s.word===doc.stage);heading.textContent=doc.stageInvalid?'Invalid metadata':doc.stage??'Unset · no stage';section.append(heading);if(definition?.meaning){const meaning=document.createElement('p');meaning.textContent=definition.meaning;section.append(meaning);}entries.push(section);previous=key;}entries.push(button(doc));}
43
- $('plans').replaceChildren(...entries);if(!state.plans.length){const empty=document.createElement('p');empty.className='library-no-results';empty.textContent='No documents match these filters. Try another search or clear the filters.';$('plans').append(empty);}
57
+ $('plans').replaceChildren(...entries);if(!state.plans.length){const empty=document.createElement('p');empty.className='library-no-results';empty.textContent=result.pendingSemantic?'Press Search to find semantic matches.':'No documents match these filters. Try another search or clear the filters.';$('plans').append(empty);}
44
58
  $('plan-count').textContent=result.counts.all.toLocaleString();$('library-range').textContent=result.semantic?`${result.total} top semantic matches · Relevance order`:result.pendingSemantic?'Press Search to find semantic matches':result.total?`${result.offset+1}–${Math.min(result.total,result.offset+result.documents.length)} of ${result.total.toLocaleString()}`:'0 results';
45
59
  $('library-previous').disabled=result.offset===0;$('library-next').disabled=!result.hasMore;
46
60
  for(const kind of ['all','hubs','plans','documents']){$('library-'+kind+'-count').textContent=result.counts[kind].toLocaleString();$('library-'+kind).setAttribute('aria-pressed',String(state.mode==='home'&&($('library-kind').value||'all')===kind));}
47
- const titles={all:'All documents',hubs:'Hubs',plans:'Plans',documents:'Docs'},descriptions={all:'Hubs, plans, and reference material across this checkout.',hubs:'Programs, roadmaps, and runlists that connect the work.',plans:'Work across this checkout, from planned to complete.',documents:'Guides, research, and other reference material.'};
61
+ const titles={all:'All documents',hubs:'Hubs',plans:'Plans',documents:'Documents'},descriptions={all:'Hubs, plans, and reference material across this checkout.',hubs:'Organize related plans and documents in one place.',plans:'Work across this checkout, from planned to complete.',documents:'Guides, research, and other reference material.'};
48
62
  const kind=$('library-kind').value||'all';$('library-title').textContent=titles[kind];$('library-description').textContent=descriptions[kind];
49
63
  if(state.mode==='home')$('doc-title').textContent=titles[kind];
50
64
  const filters=['library-status','library-type','library-folder',...(kind==='plans'?['library-stage']:[])].filter(id=>$(id).value).length+Number($('library-archived').checked);
51
- $('library-filter-label').textContent=filters?`Filters · ${filters}`:'Filters';
52
- $('library-saved').replaceChildren(...favorites.map(doc=>{const row=document.createElement('div');row.className='saved-document';const remove=document.createElement('button');remove.className='quiet small';remove.textContent='×';remove.setAttribute('aria-label',`Unpin ${doc.title}`);remove.onclick=()=>{favorites=favorites.filter(f=>f.path!==doc.path);keep();render();};row.append(button(doc,true),remove);return row;}));$('library-recent').replaceChildren(...recent.filter(r=>!favorites.some(f=>f.path===r.path)).slice(0,6).map(doc=>button(doc,true)));$('library-saved-section').hidden=!favorites.length;$('library-recent-section').hidden=!recent.length;
53
- $('pin-document').hidden=!state.doc;const pinned=!!state.doc&&favorites.some(r=>r.path===state.doc.path);$('pin-document').textContent=pinned?'★':'☆';$('pin-document').setAttribute('aria-label',pinned?'Unpin document':'Pin document');$('pin-document').setAttribute('aria-pressed',String(pinned));
65
+ const searchMode=$('library-search-semantic').checked?'Semantic search':$('library-search-content').checked?'Contents search':'';
66
+ $('library-filter-label').textContent=['Filters & views',filters?`${filters} active`:null,searchMode].filter(Boolean).join(' · ');
54
67
  $('library-scan-note').textContent=[result.semantic?`Experimental semantic search. ${result.message}${result.coverage?.partial?' Index coverage is incomplete.':''}${result.indexUpdating?' Index is updating.':''}${result.stale?' Changed sources open at the document.':''}`:'',result.partial?`Content search is partial: scanned ${result.scanned} of ${result.inventoryTotal} documents. Narrow the folder or filters to search the rest.`:'',result.unavailable?`${result.unavailable} files unavailable to content search.`:'',result.stats.unavailable||result.stats.discoveryErrors?`${result.stats.unavailable} unavailable files · ${result.stats.discoveryErrors} unreadable folders. Refresh to retry.`:''].filter(Boolean).join(' ');
55
68
  }
56
69
  function options(id,values,label){const select=$(id),value=select.value;select.replaceChildren(new Option(label,''),...values.map(v=>new Option(`${v.label??v.value}${v.unknown?' · unknown':''} (${v.count})`,v.value)));if(value&&!values.some(v=>v.value===value))select.add(new Option(`${value} (0)`,value));select.value=value;}
57
70
  async function load({refresh=false,reset=false,semanticRun=false}={}){
58
- if(reset)offset=0;semantic?.abort();semantic=null;const own=++sequence;stageControls();semanticControls();
71
+ clearTimeout(timer);if(reset)offset=0;semantic?.abort();semantic=null;const own=++sequence;stageControls();semanticControls();
59
72
  const params=new URLSearchParams({q:$('filter').value,kind:$('library-kind').value,status:$('library-status').value,type:$('library-type').value,folder:$('library-folder').value,sort:$('library-sort').value,stage:$('library-kind').value==='plans'?$('library-stage').value:'',group:$('library-kind').value==='plans'?$('library-group').value:'',archived:$('library-archived').checked?'1':'0',content:$('library-search-content').checked?'1':'0',offset:String(offset),limit:'50'});if(refresh)params.set('refresh','1');const semanticMode=$('library-search-semantic').checked,query=$('filter').value.trim();if(semanticMode){params.delete('q');params.set('content','0');params.set('offset','0');}
60
- $('plans').setAttribute('aria-busy','true');$('library-refresh').disabled=true;$('library-range').textContent='Loading documents…';
73
+ clearResults('Loading documents…');$('plans').setAttribute('aria-busy','true');$('library-refresh').disabled=true;
61
74
  try{let response=await api(`library?${params}`);if(own!==sequence)return;
62
75
  if(semanticMode&&query){
63
- if(semanticRun){const controller=new AbortController();semantic=controller;$('library-semantic-cancel').hidden=false;const found=await semanticSearch(api,{query,kind:$('library-kind').value,status:$('library-status').value,type:$('library-type').value,folder:$('library-folder').value,archived:$('library-archived').checked,stage:$('library-kind').value==='plans'?$('library-stage').value:''},controller.signal);if(own!==sequence||controller.signal.aborted)return;response={...response,...found,group:null,documents:found.documents??[],total:found.documents?.length??0,offset:0,hasMore:false,semantic:true};}
76
+ if(semanticRun){const controller=new AbortController();semantic=controller;$('library-semantic-cancel').hidden=false;const found=await semanticSearch(api,{query,kind:$('library-kind').value,status:$('library-status').value,type:$('library-type').value,folder:$('library-folder').value,archived:$('library-archived').checked,stage:$('library-kind').value==='plans'?$('library-stage').value:''},controller.signal);if(own!==sequence||controller.signal.aborted)return;if(found.state&&found.state!=='ready')throw new Error(found.message||'Semantic search is unavailable. Try exact search or retry.');response={...response,...found,group:null,documents:found.documents??[],total:found.documents?.length??0,offset:0,hasMore:false,semantic:true};}
64
77
  else response={...response,documents:[],total:0,offset:0,hasMore:false,pendingSemantic:true};
65
78
  }
66
79
  result=response;state.plans=result.documents;offset=result.offset;options('library-stage',result.facets.stages??[],'All stages');options('library-status',result.facets.statuses,'All statuses');options('library-type',result.facets.types,'All types');options('library-folder',result.facets.folders,'All locations');render();$('plans').scrollTop=0;return result;}
67
- catch(error){if(own===sequence){$('library-range').textContent='Library unavailable · try Refresh';notice(error.message,true);}throw error;}
80
+ catch(error){if(own===sequence)clearResults(query?'Search unavailable':'Library unavailable',{error,retry:()=>run({refresh,semanticRun})});throw error;}
68
81
  finally{if(own===sequence){$('plans').setAttribute('aria-busy','false');$('library-refresh').disabled=false;semantic=null;$('library-semantic-cancel').hidden=true;}}
69
82
  }
70
83
  function stageControls(){const plans=$('library-kind').value==='plans';$('library-stage-field').hidden=!plans;$('library-group-field').hidden=!plans;}
@@ -72,8 +85,8 @@ export function libraryNavigation({state,$,api,open,showLibrary,checkout,notice}
72
85
  $('library-semantic-search').onclick=()=>run({reset:true,semanticRun:true});
73
86
  $('library-semantic-cancel').onclick=()=>{changed();run({reset:true});};
74
87
  const run=options=>load(options).catch(()=>{});
75
- function changed(){semantic?.abort();semantic=null;sequence++;$('library-semantic-cancel').hidden=true;$('plans').setAttribute('aria-busy','false');$('library-refresh').disabled=false; $('library-view').value='';viewOptions(); }
76
- $('filter').oninput=()=>{changed();clearTimeout(timer);if($('library-search-semantic').checked){if(result){result={...result,documents:[],total:0,offset:0,hasMore:false,semantic:false,pendingSemantic:true};state.plans=[];render();}}else timer=setTimeout(()=>run({reset:true}),250);};
88
+ function changed(){clearTimeout(timer);semantic?.abort();semantic=null;sequence++;clearResults($('library-search-semantic').checked?$('filter').value.trim()?'Press Search to find semantic matches':'Enter a search query':'Loading documents…');$('library-semantic-cancel').hidden=true;$('plans').setAttribute('aria-busy','false');$('library-refresh').disabled=false; $('library-view').value='';viewOptions(); }
89
+ $('filter').oninput=()=>{changed();clearTimeout(timer);if(!$('library-search-semantic').checked)timer=setTimeout(()=>run({reset:true}),250);};
77
90
  for(const id of ['library-status','library-type','library-folder','library-sort','library-stage','library-group','library-archived','library-search-content','library-search-semantic'])$(id).onchange=()=>{if(id==='library-search-semantic'&&$('library-search-semantic').checked)$('library-search-content').checked=false;if(id==='library-search-content'&&$('library-search-content').checked)$('library-search-semantic').checked=false;changed();semanticControls();run({reset:true});};
78
91
  for(const kind of ['all','hubs','plans','documents'])$('library-'+kind).onclick=async()=>{try{await showLibrary();$('library-kind').value=kind;changed();await load({reset:true});}catch(error){notice(error.message,true);}};
79
92
  $('library-previous').onclick=()=>{offset=Math.max(0,offset-50);run({});};$('library-next').onclick=()=>{offset+=50;run({});};$('library-refresh').onclick=()=>run({refresh:true});
@@ -82,12 +95,12 @@ export function libraryNavigation({state,$,api,open,showLibrary,checkout,notice}
82
95
  $('save-library-view').onclick=()=>{$('filter-feedback').textContent='';$('filter-name').value='';$('filter-dialog').showModal();$('filter-name').focus();};
83
96
  $('filter-close').onclick=()=>$('filter-dialog').close();
84
97
  $('filter-form').onsubmit=event=>{event.preventDefault();try{const name=$('filter-name').value.trim();if(!name||name.length>60)throw new Error('Give this view a name, up to 60 characters.');if(savedViews.length>=30)throw new Error('Remove an old saved view before adding another.');const value={id:crypto.randomUUID(),name,filters:snapshot()},next=[...savedViews,value];localStorage.setItem(viewKey(),JSON.stringify(next));savedViews=next;viewOptions();$('library-view').value=value.id;viewOptions();$('filter-dialog').close();notice('Library view saved on this computer.');}catch(error){$('filter-feedback').textContent=error.message;}};
85
- $('delete-library-view').onclick=()=>{try{const next=savedViews.filter(v=>v.id!==$('library-view').value);localStorage.setItem(viewKey(),JSON.stringify(next));savedViews=next;changed();notice('Saved view deleted. Current filters are kept.');}catch(error){notice(error.message,true);}};
86
- $('pin-document').onclick=()=>{const doc=state.doc,index=favorites.findIndex(r=>r.path===doc.path);if(index>=0)favorites.splice(index,1);else favorites.unshift(summary(doc));favorites=favorites.slice(0,30);keep();render();};
98
+ $('delete-library-view').onclick=()=>{try{const next=savedViews.filter(v=>v.id!==$('library-view').value);localStorage.setItem(viewKey(),JSON.stringify(next));savedViews=next;$('library-view').value='';viewOptions();notice('Saved view deleted. Current filters are kept.');}catch(error){notice(error.message,true);}};
99
+ $('pin-document').onclick=()=>{const doc=state.doc;if(!doc)return;const pinned=favorites.some(r=>r.path===doc.path);setFavorites(pinned?favorites.filter(r=>r.path!==doc.path):[summary(doc),...favorites].slice(0,30));};
87
100
  function summary(doc){return {path:doc.path,title:doc.title,status:doc.status,type:doc.type,kind:doc.kind};}
88
101
  function opened(){const doc=summary(state.doc);recent=[doc,...recent.filter(r=>r.path!==doc.path)].slice(0,12);favorites=favorites.map(r=>r.path===doc.path?doc:r);keep();render();}
89
102
  function restore(){try{const saved=JSON.parse(localStorage.getItem(storageKey())??'{}'),valid=r=>r&&typeof r.path==='string'&&typeof r.title==='string';recent=Array.isArray(saved.recent)?saved.recent.filter(valid).slice(0,12):[];favorites=Array.isArray(saved.favorites)?saved.favorites.filter(valid).slice(0,30):[];const views=JSON.parse(localStorage.getItem(viewKey())??'[]');savedViews=Array.isArray(views)?views.filter(v=>v&&typeof v.id==='string'&&typeof v.name==='string'&&v.filters).slice(0,30):[];}catch{}viewOptions();render();}
90
103
  async function preferences(prefs,reload=true){$('library-sort').value=prefs.sort;$('library-archived').checked=prefs.archived;if(reload&&state.mode==='home')await load({reset:true});}
91
- function relocated(oldPath,newPath){if(oldPath===newPath)return;favorites=favorites.map(doc=>doc.path===oldPath?summary({...doc,...state.doc,path:newPath}):doc);recent=recent.filter(doc=>doc.path!==oldPath);keep();render();const oldKey=`runlist:bookmarks:${checkout()}:${encodeURIComponent(oldPath)}`,newKey=`runlist:bookmarks:${checkout()}:${encodeURIComponent(newPath)}`;try{const bookmarks=localStorage.getItem(oldKey);if(bookmarks&&!localStorage.getItem(newKey))localStorage.setItem(newKey,bookmarks);}catch{notice('Moved document bookmarks could not be copied in local editor storage.',true);}}
104
+ function relocated(oldPath,newPath){if(oldPath===newPath)return;favorites=favorites.map(doc=>doc.path===oldPath?summary({...doc,...state.doc,path:newPath}):doc);recent=recent.filter(doc=>doc.path!==oldPath);keep();render();}
92
105
  return {load,render,opened,restore,preferences,relocated,applyView,savedViews:()=>savedViews,quickDocuments:()=>[...favorites,...recent.filter(doc=>!favorites.some(f=>f.path===doc.path))].slice(0,12)};
93
106
  }