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.
- package/README.md +170 -43
- package/assets/app/app.css +189 -3
- package/assets/app/app.mjs +49 -31
- package/assets/app/document-create.mjs +3 -2
- package/assets/app/document-lifecycle.mjs +17 -8
- package/assets/app/document-stages.mjs +3 -1
- package/assets/app/document-views.mjs +23 -1
- package/assets/app/editor-navigation.mjs +16 -23
- package/assets/app/filing-navigation.mjs +18 -16
- package/assets/app/git-changes.mjs +11 -2
- package/assets/app/index.html +34 -23
- package/assets/app/library-navigation.mjs +29 -16
- package/assets/app/record-navigation.mjs +68 -23
- package/assets/app/recovery-center.mjs +8 -7
- package/assets/app/settings.mjs +48 -10
- package/assets/app/template-editor.mjs +22 -12
- package/assets/app/transport.mjs +1 -2
- package/bin/runlist.mjs +64 -15
- package/package.json +1 -1
- package/plugins/runlist-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/runlist-codex/skills/runlist/SKILL.md +3 -1
- package/src/app.mjs +9 -4
- package/src/baton.mjs +49 -11
- package/src/brief.mjs +145 -0
- package/src/commands.mjs +4 -3
- package/src/config.mjs +16 -26
- package/src/doctor.mjs +15 -0
- package/src/extractors.mjs +7 -14
- package/src/flags.mjs +5 -3
- package/src/hud.mjs +32 -63
- package/src/index.mjs +2 -0
- package/src/install.mjs +6 -4
- package/src/installation.mjs +117 -0
- package/src/new.mjs +27 -7
- package/src/prompt-handoff.mjs +132 -0
- package/src/prompts.mjs +112 -16
- package/src/section.mjs +46 -11
- package/src/show.mjs +132 -16
- package/src/template-store.mjs +16 -5
- package/src/update.mjs +7 -28
- 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', '
|
|
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', '
|
|
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
|
-
|
|
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' ? `
|
|
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 = '
|
|
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 = `
|
|
44
|
-
else if (!result.complete) feedback.textContent = result.reason ?? 'Scan incomplete.
|
|
45
|
-
else feedback.textContent = `${result.totals.plans}
|
|
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', '
|
|
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', '
|
|
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
|
|
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
|
|
80
|
-
if (!result.homes.length) nodes.push(element('p', result.complete ? '
|
|
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', `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;}}
|
package/assets/app/index.html
CHANGED
|
@@ -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"
|
|
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>
|
|
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
|
|
23
|
-
<div class="sidebar-bottom"><button id="open-templates" class="template-launch quiet">Templates
|
|
28
|
+
<div class="sidebar-group"><div class="sidebar-label">REVIEW & 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="
|
|
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="
|
|
29
|
-
<
|
|
30
|
-
<
|
|
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 & 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
|
|
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>
|
|
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
|
|
44
|
-
<div class="changes-footer"><p id="changes-staging" class="muted"></p
|
|
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="
|
|
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>
|
|
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><
|
|
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"><
|
|
57
|
-
<section id="source-view" role="tabpanel" aria-labelledby="tab-source" tabindex="0" class="source-view" hidden><div class="editor-caption"><span>
|
|
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">
|
|
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 & 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
|
|
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
|
|
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('
|
|
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:'
|
|
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
|
-
|
|
52
|
-
$('library-
|
|
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
|
-
|
|
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)
|
|
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
|
|
76
|
-
$('filter').oninput=()=>{changed();clearTimeout(timer);if(
|
|
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;
|
|
86
|
-
$('pin-document').onclick=()=>{const doc=state.doc
|
|
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();
|
|
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
|
}
|