toolroll 0.9.85 → 0.9.87
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/CHANGELOG.md +83 -0
- package/dist/agent-fence.d.ts +6 -2
- package/dist/agent-fence.js +3 -2
- package/dist/agents-board.d.ts +12 -0
- package/dist/agents-board.js +26 -0
- package/dist/approval-rules-ui.d.ts +4 -1
- package/dist/approval-rules-ui.js +31 -15
- package/dist/assignment-ui.js +1 -1
- package/dist/assignment.js +4 -3
- package/dist/backup-ui.d.ts +5 -4
- package/dist/backup-ui.js +65 -32
- package/dist/browser/terminal.css +1 -1
- package/dist/browser/terminal.js +57 -54
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +35 -68
- package/dist/browser-crew.js +1 -1
- package/dist/browser-workspace.d.ts +363 -177
- package/dist/browser-workspace.js +10 -9
- package/dist/chat-actions.d.ts +11 -1
- package/dist/chat-actions.js +53 -1
- package/dist/chat-channel.js +1 -0
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/chat-quiet.js +3 -2
- package/dist/chat-settings-page.d.ts +29 -9
- package/dist/chat-settings-page.js +59 -28
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +67 -58
- package/dist/claim.d.ts +3 -0
- package/dist/claim.js +4 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +3 -0
- package/dist/contracts/cli.d.ts +43 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +4 -1
- package/dist/contracts/console-api.js +4 -1
- package/dist/contracts/flow-propose.d.ts +4 -1
- package/dist/contracts/flow-propose.js +1 -1
- package/dist/contracts/flow.d.ts +41 -0
- package/dist/contracts/flow.js +4 -2
- package/dist/contracts/lead-tools.d.ts +21 -0
- package/dist/contracts/lead-tools.js +2 -0
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/trigger-payloads.d.ts +20 -0
- package/dist/contracts/trigger-payloads.js +4 -0
- package/dist/coordinator.js +1 -0
- package/dist/credentials-ui.d.ts +5 -3
- package/dist/credentials-ui.js +58 -31
- package/dist/deploy-gate.js +4 -0
- package/dist/design/kit.css +685 -0
- package/dist/design/kit.d.ts +208 -0
- package/dist/design/kit.js +177 -0
- package/dist/design/tokens.css +19 -1
- package/dist/discord-settings.js +8 -4
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +4 -1
- package/dist/export-ui.d.ts +6 -4
- package/dist/export-ui.js +18 -8
- package/dist/flow-browser.d.ts +103 -0
- package/dist/flow-browser.js +204 -0
- package/dist/flow-changed.d.ts +14 -0
- package/dist/flow-changed.js +53 -0
- package/dist/flow-code.d.ts +5 -1
- package/dist/flow-code.js +4 -1
- package/dist/flow-engine.js +16 -0
- package/dist/flow-gallery-ui.d.ts +41 -4
- package/dist/flow-gallery-ui.js +95 -63
- package/dist/flow-live.js +9 -1
- package/dist/flow-pull-request.js +15 -7
- package/dist/flow-share.js +3 -1
- package/dist/flow-starters-ui.d.ts +3 -9
- package/dist/flow-starters-ui.js +12 -23
- package/dist/flow-steps.d.ts +9 -1
- package/dist/flow-steps.js +55 -5
- package/dist/flow-triggers.js +55 -0
- package/dist/flow-unattended-words.d.ts +20 -0
- package/dist/flow-unattended-words.js +24 -0
- package/dist/flow-unattended.d.ts +9 -5
- package/dist/flow-unattended.js +45 -23
- package/dist/flows-cli.d.ts +19 -0
- package/dist/flows-cli.js +54 -5
- package/dist/flows-ui.d.ts +12 -6
- package/dist/flows-ui.js +152 -32
- package/dist/flows.js +5 -2
- package/dist/integrations-ui.d.ts +6 -4
- package/dist/integrations-ui.js +34 -40
- package/dist/integrations.d.ts +2 -2
- package/dist/integrations.js +4 -4
- package/dist/kits-ui.d.ts +8 -10
- package/dist/kits-ui.js +29 -33
- package/dist/kits.js +1 -1
- package/dist/knowledge-ui.js +76 -19
- package/dist/lead-tools.js +35 -6
- package/dist/lead-voice.js +16 -5
- package/dist/ledger-view.d.ts +2 -0
- package/dist/ledger-view.js +41 -38
- package/dist/models-ui.d.ts +8 -3
- package/dist/models-ui.js +82 -59
- package/dist/monitoring-ui.d.ts +4 -2
- package/dist/monitoring-ui.js +44 -13
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +8 -0
- package/dist/operate-remote-arguments.js +1 -0
- package/dist/operate-remote.js +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +249 -6
- package/dist/passwordless.d.ts +1 -1
- package/dist/passwordless.js +1 -1
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +25 -23
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +27 -21
- package/dist/policy.d.ts +3 -0
- package/dist/policy.js +14 -1
- package/dist/project-delete-ui.d.ts +4 -2
- package/dist/project-delete-ui.js +135 -126
- package/dist/project-delete.js +7 -3
- package/dist/project-knowledge.d.ts +10 -10
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +16 -3
- package/dist/pull-request-flow.js +35 -4
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +39 -28
- package/dist/recipe-ui.d.ts +4 -2
- package/dist/recipe-ui.js +14 -4
- package/dist/release-adapters.d.ts +3 -0
- package/dist/release-adapters.js +22 -2
- package/dist/release-cli.js +81 -9
- package/dist/release.d.ts +21 -0
- package/dist/release.js +40 -4
- package/dist/releases.js +1 -1
- package/dist/request-budget-ui.d.ts +4 -2
- package/dist/request-budget-ui.js +30 -19
- package/dist/result-follow-ups.js +4 -0
- package/dist/retention-ui.d.ts +4 -3
- package/dist/retention-ui.js +15 -12
- package/dist/scope.d.ts +8 -0
- package/dist/scope.js +13 -0
- package/dist/serve.d.ts +0 -1
- package/dist/serve.js +51 -7
- package/dist/server/agents-computers.d.ts +9 -0
- package/dist/server/agents-computers.js +67 -0
- package/dist/server/chat.js +20 -18
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +19 -9
- package/dist/server/console.css +31 -2
- package/dist/server/flows.js +79 -36
- package/dist/server/home.d.ts +35 -0
- package/dist/server/home.js +104 -0
- package/dist/server/http.d.ts +3 -0
- package/dist/server/http.js +5 -0
- package/dist/server/kit-standalone.d.ts +22 -0
- package/dist/server/kit-standalone.js +69 -0
- package/dist/server/kit.d.ts +405 -0
- package/dist/server/kit.js +361 -0
- package/dist/server/pages.d.ts +2 -1
- package/dist/server/pages.js +213 -174
- package/dist/server/people-tokens.js +26 -85
- package/dist/server/render-history.d.ts +51 -0
- package/dist/server/render-history.js +228 -0
- package/dist/server/render-new-task.d.ts +29 -0
- package/dist/server/render-new-task.js +96 -0
- package/dist/server/render-next.d.ts +63 -0
- package/dist/server/render-next.js +109 -0
- package/dist/server/render-pages.d.ts +35 -190
- package/dist/server/render-pages.js +154 -563
- package/dist/server/render-people.d.ts +24 -3
- package/dist/server/render-people.js +93 -72
- package/dist/server/render-projects.d.ts +103 -0
- package/dist/server/render-projects.js +247 -0
- package/dist/server/render-results.d.ts +5 -24
- package/dist/server/render-results.js +48 -185
- package/dist/server/render-settings.d.ts +5 -74
- package/dist/server/render-settings.js +102 -493
- package/dist/server/render-tasks.d.ts +43 -5
- package/dist/server/render-tasks.js +131 -91
- package/dist/server/route-table.js +31 -5
- package/dist/server/settings.js +276 -255
- package/dist/server/task-views.d.ts +8 -0
- package/dist/server/task-views.js +90 -2
- package/dist/server/tasks.d.ts +18 -0
- package/dist/server/tasks.js +148 -41
- package/dist/server/terminals.js +1 -1
- package/dist/server/welcome.d.ts +22 -0
- package/dist/server/welcome.js +69 -0
- package/dist/settings-groups-ui.d.ts +279 -0
- package/dist/settings-groups-ui.js +615 -0
- package/dist/settings-kit.d.ts +42 -6
- package/dist/settings-kit.js +69 -7
- package/dist/settings-proofs.js +1 -0
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.js +59 -7
- package/dist/slack-settings.js +12 -5
- package/dist/spend-ui.d.ts +45 -26
- package/dist/spend-ui.js +124 -56
- package/dist/sso-ui.d.ts +5 -2
- package/dist/sso-ui.js +63 -31
- package/dist/storage-ui.d.ts +3 -2
- package/dist/storage-ui.js +56 -50
- package/dist/store.d.ts +182 -5
- package/dist/store.js +512 -38
- package/dist/surface.js +23 -3
- package/dist/task-archive.d.ts +10 -0
- package/dist/task-archive.js +16 -0
- package/dist/task-control.d.ts +1 -1
- package/dist/teams-settings.d.ts +2 -1
- package/dist/teams-settings.js +24 -41
- package/dist/telegram-settings.d.ts +0 -4
- package/dist/telegram-settings.js +52 -25
- package/dist/tidy.d.ts +130 -0
- package/dist/tidy.js +503 -0
- package/dist/toolroll-update-ui.d.ts +8 -4
- package/dist/toolroll-update-ui.js +55 -45
- package/dist/tools-ui.d.ts +5 -3
- package/dist/tools-ui.js +107 -42
- package/dist/work-index.d.ts +24 -0
- package/dist/work-index.js +56 -30
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +8 -4
- package/package.json +1 -1
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The revamp kit for server pages: the same markup the React kit (src/browser/kit/) renders, as `Html`, so a
|
|
3
|
+
* server page and a React view built from the kit look and behave alike. Rules: src/design/KIT.md. Look:
|
|
4
|
+
* src/design/kit.css (served with console.css). Words and approval rules: src/design/kit.ts.
|
|
5
|
+
*
|
|
6
|
+
* Everything here escapes by construction (html.ts): strings are text, only `Html` is markup. Behaviour (the
|
|
7
|
+
* composer growing, Stop, the keyboard, overlays, switches) comes from the workspace bundle's kit behaviour
|
|
8
|
+
* (src/browser/kit/behavior.ts), which wires any kit markup the workspace places; every control here still works
|
|
9
|
+
* as plain HTML without it.
|
|
10
|
+
*/
|
|
11
|
+
import { attributes, html, joinHtml, passwordlessRendering, postForm } from "../html.js";
|
|
12
|
+
import { KIT_ICONS, NOT_FOUND, SETTINGS_GROUPS, STATUS, STEP_WORDS, TIMING_WORDS, approvalAsk, chipOfHeadline, dur, needsWords, stepsFill, termsOf, timingBars, } from "../design/kit.js";
|
|
13
|
+
const cls = (...names) => names.filter(Boolean).join(" ");
|
|
14
|
+
// ---- text and icons ---------------------------------------------------------------------------------------------
|
|
15
|
+
export function icon(name, small = false) {
|
|
16
|
+
const shapes = KIT_ICONS[name].map(shape => shape[0] === "path" ? html `<path${attributes(shape[1])}/>` : shape[0] === "circle" ? html `<circle${attributes(shape[1])}/>` : html `<rect${attributes(shape[1])}/>`);
|
|
17
|
+
return html `<svg class="${cls("k-i", small && "k-i-sm")}" viewBox="0 0 24 24" aria-hidden="true" focusable="false">${shapes}</svg>`;
|
|
18
|
+
}
|
|
19
|
+
export function btn(label, options = {}) {
|
|
20
|
+
const variant = options.variant ?? "outline";
|
|
21
|
+
const className = cls("k-btn", `k-btn-${variant}`, options.sm && "k-btn-sm", options.iconOnly && "k-btn-icon", options.docked && "k-docked", options.className);
|
|
22
|
+
const face = options.iconOnly ? icon(options.iconOnly) : html `<span class="k-face">${options.lead ? icon(options.lead, true) : ""}${label}</span>`;
|
|
23
|
+
const named = options.iconOnly ? { "aria-label": label, title: label } : {};
|
|
24
|
+
if (options.href !== undefined)
|
|
25
|
+
return html `<a class="${className}" href="${options.href}"${attributes({ ...named, ...options.attrs })}>${face}</a>`;
|
|
26
|
+
return html `<button class="${className}" type="${options.type ?? "button"}"${attributes({ ...named, ...options.attrs })}>${face}</button>`;
|
|
27
|
+
}
|
|
28
|
+
// ---- status -----------------------------------------------------------------------------------------------------
|
|
29
|
+
export function chip(word, tone = "", options = {}) {
|
|
30
|
+
return html `<span class="${cls("k-chip", tone, options.dot === false && "nodot")}">${word}</span>`;
|
|
31
|
+
}
|
|
32
|
+
/** One of the five status words in its tone. */
|
|
33
|
+
export const status = (key) => chip(STATUS[key].word, STATUS[key].tone);
|
|
34
|
+
/** A task's headline (task-status.ts) as its chip. */
|
|
35
|
+
export function headlineChip(headline) {
|
|
36
|
+
const { word, tone } = chipOfHeadline(headline);
|
|
37
|
+
return chip(word, tone);
|
|
38
|
+
}
|
|
39
|
+
/** A count pill: ink only for a Needs-you count; zero is muted. */
|
|
40
|
+
export function count(n, options = {}) {
|
|
41
|
+
return html `<span class="${cls("k-count", options.ink && n > 0 && "ink", n === 0 && "zero")}">${n}</span>`;
|
|
42
|
+
}
|
|
43
|
+
/** Sections of one object as addresses: a <nav> of links, the current one aria-current="page". */
|
|
44
|
+
export function linkTabs(label, current, items) {
|
|
45
|
+
return html `<nav class="k-tabs" aria-label="${label}">${items.map(item => html `<a href="${item.href}"${item.key === current ? html ` aria-current="page"` : ""}>${item.label}${item.count ?? ""}</a>`)}</nav>`;
|
|
46
|
+
}
|
|
47
|
+
/** Filters or views of a list as addresses: the segmented track. */
|
|
48
|
+
export function segLinks(label, current, items) {
|
|
49
|
+
return html `<nav class="k-seg" aria-label="${label}">${items.map(item => html `<a href="${item.href}"${item.key === current ? html ` aria-current="page"` : ""}>${item.label}${item.count ?? ""}</a>`)}</nav>`;
|
|
50
|
+
}
|
|
51
|
+
/** Filters or views as addresses with icons (List / Board): each item an icon, its label read aloud. */
|
|
52
|
+
export function iconSegLinks(label, current, items) {
|
|
53
|
+
return html `<nav class="k-seg k-seg-icons" aria-label="${label}">${items.map(item => html `<a href="${item.href}" aria-label="${item.label}" title="${item.label}"${item.key === current ? html ` aria-current="page"` : ""}>${icon(item.icon)}</a>`)}</nav>`;
|
|
54
|
+
}
|
|
55
|
+
/** A list's text filter (the workspace narrows the rows on the page as it is typed; without it, a GET form). */
|
|
56
|
+
export function filterInput(options) {
|
|
57
|
+
return html `<label class="k-filter">${icon("search", true)}<input type="search"${attributes({ name: options.name, value: options.value, placeholder: options.placeholder ?? options.label, "aria-label": options.label })}></label>`;
|
|
58
|
+
}
|
|
59
|
+
// ---- fields -----------------------------------------------------------------------------------------------------
|
|
60
|
+
/** A field: label, control, one hint line, and the error slot tied to the control (aria-describedby). */
|
|
61
|
+
export function field(options) {
|
|
62
|
+
return html `<div class="k-field"><label class="k-label" for="${options.id}">${options.label}</label>${options.control}\
|
|
63
|
+
${options.hint ? html `<p class="k-hint" id="${options.id}-hint">${options.hint}</p>` : ""}\
|
|
64
|
+
<span class="k-err" id="${options.id}-err" role="alert">${options.error ?? ""}</span></div>`;
|
|
65
|
+
}
|
|
66
|
+
/** A text input; `error` marks it invalid and points it at its error line. */
|
|
67
|
+
export function input(options) {
|
|
68
|
+
const describedBy = [options.hint && `${options.id}-hint`, `${options.id}-err`].filter(Boolean).join(" ");
|
|
69
|
+
return html `<input class="k-input" id="${options.id}" name="${options.name}" type="${options.type ?? "text"}"${attributes({ value: options.value, "aria-invalid": options.error ? "true" : undefined, "aria-describedby": describedBy, ...options.attrs })}>`;
|
|
70
|
+
}
|
|
71
|
+
export function textarea(options) {
|
|
72
|
+
return html `<textarea class="k-textarea" id="${options.id}" name="${options.name}" rows="${options.rows ?? 3}"${attributes({ "aria-invalid": options.error ? "true" : undefined, "aria-describedby": `${options.id}-err`, ...options.attrs })}>${options.value ?? ""}</textarea>`;
|
|
73
|
+
}
|
|
74
|
+
/** A native select with the kit's chevron: 32px, `sm` 28px in a toolbar, 44px and 16px text on a phone. */
|
|
75
|
+
export function select(options) {
|
|
76
|
+
const opts = options.items.map(item => {
|
|
77
|
+
const [value, label] = typeof item === "string" ? [item, item] : item;
|
|
78
|
+
return html `<option value="${value}"${value === options.current ? html ` selected` : ""}>${label}</option>`;
|
|
79
|
+
});
|
|
80
|
+
return html `<span class="${cls("k-selw", options.sm && "sm", options.wide && "wide")}"><select class="k-sel"${attributes({ id: options.id, name: options.name, "aria-label": options.label, ...options.attrs })}>${opts}</select></span>`;
|
|
81
|
+
}
|
|
82
|
+
/** An on/off setting in a form: a checkbox with role=switch and a visible label (posts `name=on` when on). */
|
|
83
|
+
export function switchInput(options) {
|
|
84
|
+
return html `<label class="k-switch"><input class="k-switch-input" type="checkbox" role="switch" id="${options.id}" name="${options.name}"${options.on ? html ` checked` : ""}${attributes(options.attrs ?? {})}><span class="k-trk" aria-hidden="true"></span><span>${options.label}</span></label>`;
|
|
85
|
+
}
|
|
86
|
+
/** Two columns: label and one muted line | the control. `inline` keeps the control beside the label on a phone. */
|
|
87
|
+
export function formRow(label, hint, control, variant = "") {
|
|
88
|
+
return html `<div class="${cls("k-frow", variant)}"><div class="k-frow-l"><span class="k-frow-t">${label}</span>${hint ? html `<span class="k-frow-h">${hint}</span>` : ""}</div><div class="k-frow-c">${control}</div></div>`;
|
|
89
|
+
}
|
|
90
|
+
/** Radio cards: one choice from a few, each with a bold label and one muted line. */
|
|
91
|
+
export function radios(options) {
|
|
92
|
+
return html `<fieldset class="k-radios"><legend class="k-sr">${options.legend}</legend>${options.items.map(item => html `<label class="k-opt"><input type="radio" name="${options.name}" value="${item.value}"${item.value === options.current ? html ` checked` : ""}><span><b>${item.label}</b>${item.text ? html `<span>${item.text}</span>` : ""}</span>${item.tag ?? ""}</label>`)}</fieldset>`;
|
|
93
|
+
}
|
|
94
|
+
// ---- blocks -----------------------------------------------------------------------------------------------------
|
|
95
|
+
export const acts = (...buttons) => html `<div class="k-acts">${buttons}</div>`;
|
|
96
|
+
export function facts(rows) {
|
|
97
|
+
return html `<dl class="k-facts">${rows.map(([key, value]) => html `<dt>${key}</dt><dd>${value}</dd>`)}</dl>`;
|
|
98
|
+
}
|
|
99
|
+
export const note = (text) => html `<p class="k-note">${text}</p>`;
|
|
100
|
+
/** A refusal or problem, where it happened (role=alert): what went wrong and what didn't change. */
|
|
101
|
+
export const notice = (text) => html `<p class="k-notice" role="alert">${icon("alert", true)}<span>${text}</span></p>`;
|
|
102
|
+
/** One neutral system line (a builder offline, an update): not a failure, never amber. */
|
|
103
|
+
export const infoLine = (text) => html `<p class="k-notice info">${text}</p>`;
|
|
104
|
+
export function empty(options) {
|
|
105
|
+
return html `<div class="k-empty"><p class="k-empty-t">${options.title}</p>${options.text ? html `<p class="k-empty-x">${options.text}</p>` : ""}${options.action ?? ""}</div>`;
|
|
106
|
+
}
|
|
107
|
+
/** A disclosure for secondary detail. */
|
|
108
|
+
export const disclose = (summary, body, open = false) => html `<details class="k-disclose"${open ? html ` open` : ""}><summary>${summary}</summary>${body}</details>`;
|
|
109
|
+
// ---- lists ------------------------------------------------------------------------------------------------------
|
|
110
|
+
/** A group heading with its count; `right` puts one quiet tool at its end. */
|
|
111
|
+
export const group = (label, n, right) => html `<h2 class="k-grp">${label}${n ?? ""}${right ? html `<span class="k-grow"></span>${right}` : ""}</h2>`;
|
|
112
|
+
/** A list row: chip · title (the whole row is the link) · one muted line · meta · one action, always in one column. */
|
|
113
|
+
export function row(options) {
|
|
114
|
+
// No href: a thing with no page of its own (a computer); its title is text and its action is the way in.
|
|
115
|
+
const title = options.href === undefined ? html `<span class="k-row-t">${options.title}</span>` : html `<a class="k-row-t" href="${options.href}">${options.title}</a>`;
|
|
116
|
+
return html `<div class="${cls("k-row", options.wait && "wait", options.href === undefined && "k-row-static")}"${attributes(options.attrs ?? {})}><span class="k-row-c">${options.chip}</span>\
|
|
117
|
+
${title}<span class="k-row-d">${options.desc ?? ""}</span>\
|
|
118
|
+
<span class="k-row-m">${options.meta ?? ""}</span><span class="k-row-a">${options.action ?? ""}</span>${options.message ? html `<div class="k-row-msg">${options.message}</div>` : ""}</div>`;
|
|
119
|
+
}
|
|
120
|
+
// ---- page headers -----------------------------------------------------------------------------------------------
|
|
121
|
+
/** The phone header: back (or nothing), the title, one trailing action. Hidden at a desk. */
|
|
122
|
+
export function phoneHeader(title, options = {}) {
|
|
123
|
+
return html `<header class="k-ph">${options.back ? btn("Back", { variant: "ghost", iconOnly: "back", href: options.back }) : ""}<p class="k-ph-t">${title}</p>${options.trailing ?? ""}</header>`;
|
|
124
|
+
}
|
|
125
|
+
/** A list page's header: the phone header and the desk top bar (title · muted context · spacer · tools). */
|
|
126
|
+
export function pageHeader(title, options = {}) {
|
|
127
|
+
const phone = phoneHeader(options.phoneTitle ?? title, { ...(options.back ? { back: options.back } : {}), ...(options.trailing ? { trailing: options.trailing } : {}) });
|
|
128
|
+
return html `${phone}<div class="k-tb"><h1>${title}</h1>${options.ctx ? html `<span class="k-tb-ctx">${options.ctx}</span>` : ""}${options.right ? html `<span class="k-grow"></span>${options.right}` : ""}</div>`;
|
|
129
|
+
}
|
|
130
|
+
/** A reading page's header: a back link, the one h1, a meta line. `same` when the phone header already shows the
|
|
131
|
+
* title: the h1 stays for assistive tech and is drawn once. */
|
|
132
|
+
export function docHeader(options) {
|
|
133
|
+
return html `${options.back ? html `<a class="k-backlink" href="${options.back[0]}">${icon("back", true)}${options.back[1]}</a>` : ""}\
|
|
134
|
+
<header class="k-dh"><div><h1${options.same ? html ` class="k-same"` : ""}>${options.title}</h1>${options.meta ? html `<p class="k-dh-meta">${options.meta}</p>` : ""}</div>${options.right ?? ""}</header>`;
|
|
135
|
+
}
|
|
136
|
+
export const doc = (...parts) => html `<div class="k-doc">${parts}</div>`;
|
|
137
|
+
// ---- approval ---------------------------------------------------------------------------------------------------
|
|
138
|
+
/** The four fixed slots: status (chip + heading) · supporting text · actions · terms, plus the error slot. The same
|
|
139
|
+
* slots in every state (waiting, approved, automatic, refused), so nothing moves. `body`: the choice the act takes
|
|
140
|
+
* (radio cards, a note), between the text and the actions. */
|
|
141
|
+
export function slots(options) {
|
|
142
|
+
return html `<section class="${cls("k-slots", options.wait && "wait")}"${attributes(options.attrs ?? {})}><div class="k-slot-status">${options.status}</div>\
|
|
143
|
+
<p class="k-slot-text">${options.text}</p>${options.body === undefined ? "" : html `<div class="k-slot-body">${options.body}</div>`}<div class="k-slot-acts k-acts">${options.acts}</div><p class="k-slot-terms">${options.terms}</p>\
|
|
144
|
+
<div class="k-slot-err">${options.err ? notice(options.err) : ""}</div></section>`;
|
|
145
|
+
}
|
|
146
|
+
/** This request's approval facts: Passwordless as the request sees it, plus what the page read for this act. */
|
|
147
|
+
export function approvalFactsNow(read = { remembered: false }) {
|
|
148
|
+
return { ...read, passwordless: passwordlessRendering() };
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The control that approves one act, inside its slots: a POST form with the act's button and, only when the rules
|
|
152
|
+
* say so, the password field (name `token`, as every ceremony reads it). Automatic approval renders nothing to
|
|
153
|
+
* press. The page decides only what it shows: the server proves the person, the freshness and the scope again
|
|
154
|
+
* when the form arrives.
|
|
155
|
+
*/
|
|
156
|
+
export function approveForm(options) {
|
|
157
|
+
const ask = approvalAsk(options.kind, options.facts);
|
|
158
|
+
if (ask === "automatic")
|
|
159
|
+
return html ``;
|
|
160
|
+
const fieldId = `k-reauth-${options.action.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "")}`;
|
|
161
|
+
const password = ask === "password"
|
|
162
|
+
? html `<div class="k-reauth k-field"><label class="k-label" for="${fieldId}">Your password</label><input class="k-input" id="${fieldId}" type="password" name="token" autocomplete="current-password" required></div>`
|
|
163
|
+
: html ``;
|
|
164
|
+
const button = btn(options.label, { variant: options.primary === false ? "outline" : "primary", type: "submit", ...(options.docked ? { docked: true } : {}) });
|
|
165
|
+
// An approval is made inside a signed-in request: its CSRF field is not optional, whatever a caller passes.
|
|
166
|
+
if (options.form !== undefined && "signedOut" in options.form)
|
|
167
|
+
throw new Error("approveForm: an approval form always carries the request's CSRF field");
|
|
168
|
+
// The k-approve class is GuardedHtml's stale-approval marker: a caller's classes join it, never replace it.
|
|
169
|
+
const extra = options.form?.attrs ?? {};
|
|
170
|
+
const classes = ["k-approve", ...(typeof extra["class"] === "string" ? extra["class"].split(/\s+/).filter(name => name !== "" && name !== "k-approve") : [])].join(" ");
|
|
171
|
+
const formOptions = { ...options.form, attrs: { ...extra, class: classes, "data-approval-kind": options.kind } };
|
|
172
|
+
return postForm(options.action, html `${options.before ?? ""}${password}<div class="k-acts">${button}</div>`, formOptions);
|
|
173
|
+
}
|
|
174
|
+
/** The terms line for an act, from the same rules. */
|
|
175
|
+
export const approvalTerms = (kind, facts) => termsOf(kind, facts);
|
|
176
|
+
/** Waits that aren't tasks, on their own page (a spend limit, a failed backup): one amber outline, a count, items
|
|
177
|
+
* split by hairlines, each with its one action. They never join the global Needs-you count. */
|
|
178
|
+
export function needsPanel(items, label = "Needs you") {
|
|
179
|
+
if (items.length === 0)
|
|
180
|
+
return html ``;
|
|
181
|
+
return html `<section class="k-needs" aria-label="${label}"><h2 class="k-needs-h">${label} ${count(items.length, { ink: true })}</h2>\
|
|
182
|
+
${items.map(item => html `<div class="k-needs-item"><div><b>${item.title}</b>${item.text ? html `<p>${item.text}</p>` : ""}</div>${item.action ? html `<div class="k-acts">${item.action}</div>` : ""}</div>`)}</section>`;
|
|
183
|
+
}
|
|
184
|
+
// ---- timing -----------------------------------------------------------------------------------------------------
|
|
185
|
+
/** A run's steps on one elapsed-time axis (Release, a flow's run, a pull request's checks). */
|
|
186
|
+
export function timing(options) {
|
|
187
|
+
const bars = timingBars(options.rows, options.scale);
|
|
188
|
+
const mark = (st) => st === "wait" ? html `<span class="k-wd"></span>` : st === "q" ? html `<span class="k-qd"></span>` : st === "run" ? html `<span class="k-rund"></span>` : icon(st === "ok" ? "check" : "x", true);
|
|
189
|
+
const rows = options.rows.map((row, index) => {
|
|
190
|
+
const bar = bars[index];
|
|
191
|
+
const now = row.st === "run" || row.st === "bad" || row.st === "wait";
|
|
192
|
+
return html `<div class="${cls("k-crow", row.st, now && "now")}"><span class="k-crow-i">${mark(row.st)}</span>\
|
|
193
|
+
<span class="k-crow-n">${row.name}<span class="k-crow-sub">${row.sub ?? (row.st === "q" ? "not reached" : "")}</span></span>\
|
|
194
|
+
<span class="k-crow-lane">${bar ? html `<i class="k-crow-bar" style="left:${bar.left.toFixed(2)}%;width:${bar.width.toFixed(2)}%"></i>` : ""}</span>\
|
|
195
|
+
<span class="k-crow-w">${TIMING_WORDS[row.st]}</span><span class="k-crow-s k-num">${row.st === "q" ? "–" : dur(row.dur)}</span></div>`;
|
|
196
|
+
});
|
|
197
|
+
return html `<section class="k-timing" aria-label="${options.label ?? "Steps"}"><header class="k-timing-h"><h2>${options.label ?? "Steps"}</h2><span class="k-timing-state">${options.chip ?? ""}</span></header>\
|
|
198
|
+
<div class="k-timing-b"><div class="k-crow head" aria-hidden="true"><span></span><span>${options.head ?? "Step"}</span><span class="k-crow-lane">Elapsed time</span><span>Status</span><span class="k-crow-s">Time</span></div>${rows}</div>\
|
|
199
|
+
<div class="k-timing-f">${options.foot ?? ""}</div></section>`;
|
|
200
|
+
}
|
|
201
|
+
// ---- progress line and board -----------------------------------------------------------------------------------
|
|
202
|
+
/** A few named steps on one line, filled up to where the work is. */
|
|
203
|
+
export function steps(items, label = "Progress") {
|
|
204
|
+
return html `<ol class="k-steps" aria-label="${label}" style="--k-n:${items.length};--k-fill:${stepsFill(items).toFixed(3)}"><span class="k-steps-fill" aria-hidden="true"></span>\
|
|
205
|
+
${items.map(step => html `<li class="k-step ${step.state}"${step.state === "now" || step.state === "live" || step.state === "failed" ? html ` aria-current="step"` : ""} aria-label="${step.label}: ${STEP_WORDS[step.state]}${step.sub ? `, ${step.sub}` : ""}"><b aria-hidden="true"></b><span class="k-step-l" aria-hidden="true">${step.label}</span><span class="k-step-s" aria-hidden="true">${step.sub ?? ""}</span></li>`)}</ol>`;
|
|
206
|
+
}
|
|
207
|
+
/** The board's columns; on a phone one at a time (`on`). */
|
|
208
|
+
export const board = (label, ...columns) => html `<div class="k-board" role="region" aria-label="${label}">${columns}</div>`;
|
|
209
|
+
export function boardCol(options) {
|
|
210
|
+
return html `<section class="${cls("k-col", options.on && "on")}" aria-label="${options.label}"><h2 class="${cls("k-col-h", options.needs && "needs")}">${options.label}${options.count ?? ""}</h2>\
|
|
211
|
+
${options.cards.length > 0 ? options.cards : html `<p class="k-col-e">${options.empty}</p>`}</section>`;
|
|
212
|
+
}
|
|
213
|
+
export const card = (options) => html `<a class="k-card" href="${options.href}"><span class="k-card-t">${options.title}</span><span class="k-card-m">${options.meta}</span></a>`;
|
|
214
|
+
// ---- pages ------------------------------------------------------------------------------------------------------
|
|
215
|
+
/** The not-found page, inside the shell: what was asked for and two ways on. */
|
|
216
|
+
export function notFound(path, options = {}) {
|
|
217
|
+
return html `${pageHeader("Not found")}<div class="k-nf">${empty({
|
|
218
|
+
title: options.title ?? NOT_FOUND.title(path), text: options.text ?? NOT_FOUND.text,
|
|
219
|
+
action: acts(btn("Go to Home", { sm: true, href: "/" }), btn("Open Tasks", { sm: true, variant: "ghost", href: "/work" })),
|
|
220
|
+
})}</div>`;
|
|
221
|
+
}
|
|
222
|
+
/** The Settings frame: Overview and the eight groups, a desk breadcrumb, a phone back header, one column. */
|
|
223
|
+
export function settingsShell(options) {
|
|
224
|
+
const flags = options.flags ?? {};
|
|
225
|
+
const own = SETTINGS_GROUPS.find(([, slug]) => slug === options.group);
|
|
226
|
+
const hub = options.group === "hub" || own === undefined;
|
|
227
|
+
const nav = html `<a class="k-nav k-nav-top" href="/settings"${hub ? html ` aria-current="page"` : ""}><span class="k-nav-l">Overview</span></a>\
|
|
228
|
+
${SETTINGS_GROUPS.map(([label, slug, href]) => html `<a class="k-nav" href="${href}"${slug === options.group ? html ` aria-current="page"` : ""}><span class="k-nav-l">${label}</span>${flags[slug] ? html `<span class="k-nav-flag" aria-hidden="true"></span><span class="k-sr"> (needs you)</span>` : ""}</a>`)}`;
|
|
229
|
+
const back = options.back ?? (hub ? undefined : options.sub ? own[2] : "/settings");
|
|
230
|
+
const crumbs = hub ? html `` : options.sub
|
|
231
|
+
? html `<nav class="k-tb-ctx k-crumbs" aria-label="Breadcrumb"><a href="${own[2]}">${own[0]}</a><span class="k-sep" aria-hidden="true">/</span><b aria-current="page">${options.title}</b></nav>`
|
|
232
|
+
: html `<nav class="k-tb-ctx k-crumbs" aria-label="Breadcrumb"><b aria-current="page">${options.title}</b></nav>`;
|
|
233
|
+
return html `${phoneHeader(hub ? "Settings" : options.title, back ? { back } : {})}<div class="k-tb"><h1>Settings</h1>${crumbs}</div>\
|
|
234
|
+
<div class="k-settings"><nav class="k-snav" aria-label="Settings">${nav}</nav><div class="k-sbody"><header><h2 class="${hub ? "k-sr" : "k-same"}">${options.title}</h2>${options.lead ? html `<p>${options.lead}</p>` : ""}</header>${options.body}</div></div>`;
|
|
235
|
+
}
|
|
236
|
+
// ---- composer ---------------------------------------------------------------------------------------------------
|
|
237
|
+
/** The round send, an arrow up; with `stop` it carries its Stop twin (the composer's .working class swaps them).
|
|
238
|
+
* `gate` (a message box) marks it aria-disabled while the box is empty. */
|
|
239
|
+
export function sendButton(options = {}) {
|
|
240
|
+
const label = options.label ?? "Send";
|
|
241
|
+
const gated = options.gate === true && options.empty !== false;
|
|
242
|
+
return html `<span class="k-send-slot"><button class="k-send" type="submit" aria-label="${label}" title="${label}"${attributes({ name: options.name, "data-gate": options.gate ? "1" : undefined, "aria-disabled": gated ? "true" : undefined, ...options.attrs })}>${icon("send")}</button>\
|
|
243
|
+
${options.stop ? html `<button class="k-send k-stop" type="button" aria-label="${options.stop.label ?? "Stop"}" title="${options.stop.label ?? "Stop"}"${attributes(options.stop.attrs ?? {})}>${icon("stop")}</button>` : ""}</span>`;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The message box: one soft rounded container, the textarea grows from `rows` to `maxRows` lines then scrolls,
|
|
247
|
+
* one bottom row inside (+ menu, the project as a quiet chip, small controls, the round send). Textarea
|
|
248
|
+
* `<id>-text` (named `name`), refusal slot `<id>-err`, chosen project a hidden input `<id>-project` (named
|
|
249
|
+
* `projectName`). A refused send keeps the words in the box with the reason in the error slot.
|
|
250
|
+
*/
|
|
251
|
+
export function composer(options) {
|
|
252
|
+
const value = options.value ?? "";
|
|
253
|
+
const textId = `${options.id}-text`;
|
|
254
|
+
const project = options.project
|
|
255
|
+
? html `<button class="k-composer-chip" type="button" data-k-composer-project="${options.id}" aria-haspopup="dialog" aria-label="Project: ${options.project.current}. Change"${options.disabled ? html ` disabled` : ""}><span data-k-project-name>${options.project.current}</span>${icon("chevron", true)}</button>\
|
|
256
|
+
<input type="hidden" id="${options.id}-project" name="${options.project.name ?? "project"}" value="${options.project.current}" data-items="${JSON.stringify(options.project.items)}">`
|
|
257
|
+
: html ``;
|
|
258
|
+
const plus = options.plus
|
|
259
|
+
? html `<button class="k-composer-plus" type="button" data-k-composer-plus="${options.id}" aria-haspopup="dialog" aria-label="Add to your message"${options.disabled ? html ` disabled` : ""}>${icon("plus")}</button>`
|
|
260
|
+
: html ``;
|
|
261
|
+
return html `<div class="${cls("k-composer", options.line && "line", options.disabled && "off", value.trim() && "has-text", options.working && "working")}" data-k-composer="${options.id}">\
|
|
262
|
+
<label class="k-sr" for="${textId}">${options.label}</label><div class="k-composer-files" data-k-files="${options.id}"></div>\
|
|
263
|
+
<textarea class="k-composer-ta" id="${textId}" name="${options.name}" rows="${options.rows ?? 1}" data-min="${options.rows ?? 1}" data-max="${options.maxRows ?? 8}"${attributes({ placeholder: options.placeholder, disabled: options.disabled === true, "aria-invalid": options.error ? "true" : undefined, "aria-describedby": `${options.id}-err`, ...options.textAttrs })}>${value}</textarea>\
|
|
264
|
+
<div class="k-composer-bar">${plus}${project}${options.before ?? ""}<span class="k-grow"></span>${options.send}</div>\
|
|
265
|
+
${options.note ? html `<p class="k-note k-composer-note">${options.note}</p>` : ""}<div class="k-composer-err k-err" id="${options.id}-err" role="alert">${options.error ?? ""}</div></div>`;
|
|
266
|
+
}
|
|
267
|
+
/** A floating panel, hidden until its trigger (`overlayTrigger`) opens it: role=dialog, a labelled Close, Escape
|
|
268
|
+
* and the scrim close it, Tab stays inside, focus returns to the trigger. A popover and a menu become a bottom
|
|
269
|
+
* sheet on a phone. */
|
|
270
|
+
export function overlay(options) {
|
|
271
|
+
const titleId = `${options.id}-title`;
|
|
272
|
+
const close = btn("Close", { variant: "ghost", iconOnly: "x", sm: options.kind === "popover" || options.kind === "menu", attrs: { "data-k-close": "" } });
|
|
273
|
+
const head = options.kind === "drawer"
|
|
274
|
+
? html `<header class="k-drawer-h"><h2 id="${titleId}">${options.title}</h2>${close}</header>`
|
|
275
|
+
: options.kind === "sheet"
|
|
276
|
+
? html `<div class="k-sheet-h"><div class="k-sheet-ht"><h2 id="${titleId}">${options.title}</h2>${options.sub ? note(options.sub) : ""}</div>${close}</div>`
|
|
277
|
+
: html `<div class="k-pop-h"><h3 id="${titleId}">${options.title}</h3>${close}</div>`;
|
|
278
|
+
const body = options.kind === "menu" ? html `<div class="k-menu">${options.body}</div>` : options.body;
|
|
279
|
+
const className = options.kind === "drawer" ? "k-drawer" : options.kind === "sheet" ? "k-sheet" : cls("k-pop", options.kind === "menu" && "k-pop-menu");
|
|
280
|
+
const inner = options.kind === "drawer" ? html `${head}<div class="k-drawer-b">${body}</div>` : options.kind === "sheet" ? html `${head}<div class="k-sheet-b">${body}</div>` : html `${head}${body}`;
|
|
281
|
+
return html `<div class="${className}" id="${options.id}" role="dialog" aria-modal="true" aria-labelledby="${titleId}" data-k-overlay="${options.kind}" tabindex="-1" hidden>${inner}</div>`;
|
|
282
|
+
}
|
|
283
|
+
/** The button that opens an overlay. */
|
|
284
|
+
export function overlayTrigger(label, overlayId, options = {}) {
|
|
285
|
+
return btn(label, { ...options, attrs: { "data-k-open": overlayId, "aria-haspopup": "dialog", "aria-controls": overlayId, ...options.attrs } });
|
|
286
|
+
}
|
|
287
|
+
// ---- phone dock -------------------------------------------------------------------------------------------------
|
|
288
|
+
/** The phone dock: this screen's one primary (the same act the card hides on a phone). */
|
|
289
|
+
export function dock(primary) {
|
|
290
|
+
return primary === null ? html `` : html `<div class="k-dock"><div class="k-dock-primary">${primary}</div></div>`;
|
|
291
|
+
}
|
|
292
|
+
/** Several kit parts joined. */
|
|
293
|
+
export const parts = (...values) => joinHtml(values);
|
|
294
|
+
// ---- pages outside the shell (Wave E) ---------------------------------------------------------------------------
|
|
295
|
+
/**
|
|
296
|
+
* A page outside the shell (sign in, the first account, an invite, signed out, an error): the brand, one narrow
|
|
297
|
+
* column with the one h1, then an optional foot line under a hairline. `brand` is the product mark (chrome.ts
|
|
298
|
+
* BRAND_HTML). These pages carry no script: everything on them works as plain HTML.
|
|
299
|
+
*/
|
|
300
|
+
export function outPage(options) {
|
|
301
|
+
return html `<div class="k-out"><div class="k-out-c"><a class="k-out-brand" href="${options.brandHref ?? "/"}">${options.brand}</a>\
|
|
302
|
+
<h1>${options.title}</h1>${options.note ? html `<p class="k-out-note" role="status">${options.note}</p>` : ""}${options.sub ? html `<p class="k-out-sub">${options.sub}</p>` : ""}${options.body ?? ""}</div>\
|
|
303
|
+
${options.foot ? html `<p class="k-out-f">${options.foot}</p>` : ""}</div>`;
|
|
304
|
+
}
|
|
305
|
+
/** A form field outside the shell: label, input, one hint line, and its error line (the field is aria-invalid, described
|
|
306
|
+
* by both and focused when the error is about it). The error is a server's refusal; nothing here validates. */
|
|
307
|
+
export function outField(options) {
|
|
308
|
+
const describedBy = [options.hint ? `${options.id}-hint` : null, options.error ? `${options.id}-err` : null].filter(Boolean).join(" ");
|
|
309
|
+
const control = html `<input class="k-input" id="${options.id}" name="${options.name}" type="${options.type ?? "text"}"${attributes({
|
|
310
|
+
value: options.value, "aria-invalid": options.error ? "true" : undefined, "aria-describedby": describedBy || undefined, ...(options.error ? { autofocus: true } : {}), ...options.attrs,
|
|
311
|
+
})}>`;
|
|
312
|
+
return html `<div class="k-field"><label class="k-label" for="${options.id}">${options.label}</label>${control}\
|
|
313
|
+
${options.hint ? html `<p class="k-hint" id="${options.id}-hint">${options.hint}</p>` : ""}${options.error ? html `<p class="k-err" id="${options.id}-err" role="alert">${options.error}</p>` : ""}</div>`;
|
|
314
|
+
}
|
|
315
|
+
/** A short list of steps: a mark, the step and one line, its one action. Done steps read muted with a check; a step
|
|
316
|
+
* being checked shows a static ring (running state never animates). The state is also said in words. */
|
|
317
|
+
export function checklist(steps, label = "Steps") {
|
|
318
|
+
const word = { done: "Done", open: "Not done", checking: "Checking" };
|
|
319
|
+
return html `<ol class="k-check" aria-label="${label}">${steps.map(step => html `<li class="${cls("k-check-row", step.state === "done" && "done")}" data-step="${step.key}">\
|
|
320
|
+
<span class="${cls("k-check-mark", step.state === "done" && "ok", step.state === "checking" && "run")}" aria-hidden="true">${step.state === "done" ? icon("check", true) : ""}</span>\
|
|
321
|
+
<span class="k-check-t"><b>${step.title}</b><span>${step.text}</span><span class="k-sr">${word[step.state]}</span></span>\
|
|
322
|
+
<span class="k-check-a">${step.state === "done" ? "" : step.action ?? ""}</span></li>`)}</ol>`;
|
|
323
|
+
}
|
|
324
|
+
// ---- ideas (Wave E) ---------------------------------------------------------------------------------------------
|
|
325
|
+
/** Ideas that draft words into a composer: each is a link that opens the page with the words in the box (no script),
|
|
326
|
+
* and with the kit behaviour it adds them to the box in place (`data-k-draft`, `data-k-into` = the composer id). */
|
|
327
|
+
export function ideas(options) {
|
|
328
|
+
if (options.items.length === 0)
|
|
329
|
+
return html ``;
|
|
330
|
+
return html `<section class="k-sec"><h3>${options.label}</h3><div class="k-ideas">${options.items.map(item => html `<a class="k-btn k-btn-outline k-idea" href="${item.href}" data-k-draft="${item.draft}" data-k-into="${options.into}">${item.label}</a>`)}</div></section>`;
|
|
331
|
+
}
|
|
332
|
+
// ---- settings pages (wave D) ------------------------------------------------------------------------------------
|
|
333
|
+
/** A settings section: its heading (with an optional muted state after it), one muted lead line, then its rows.
|
|
334
|
+
* No nested wells. `id` is also the anchor other pages link to. */
|
|
335
|
+
export function settingsSection(options) {
|
|
336
|
+
const head = `${options.id}-h`;
|
|
337
|
+
return html `<section class="k-ssec" id="${options.id}" aria-labelledby="${head}"><div class="k-secthead"><h3 id="${head}">${options.title}\
|
|
338
|
+
${options.state ? html `<span class="k-ssec-state">${options.state}</span>` : ""}</h3>${options.right ?? ""}</div>\
|
|
339
|
+
${options.lead ? html `<p class="k-ssec-lead">${options.lead}</p>` : ""}${options.body}</section>`;
|
|
340
|
+
}
|
|
341
|
+
/** One row that opens a settings page: icon, title (with "1 item needs you" when something waits), one status line,
|
|
342
|
+
* anything on the right, a chevron. The whole row is the link. */
|
|
343
|
+
export function settingsLink(options) {
|
|
344
|
+
const flag = options.needs ? html `<span class="k-sflag">${needsWords(options.needs)}</span>` : "";
|
|
345
|
+
return html `<a class="${cls("k-slink", !options.icon && "noico")}" href="${options.href}">${options.icon ? icon(options.icon) : ""}\
|
|
346
|
+
<span class="k-sl"><span class="k-sl-t"><span>${options.title}</span>${flag}</span>${options.status ? html `<span class="k-sl-h">${options.status}</span>` : ""}</span>\
|
|
347
|
+
<span class="k-slink-r">${options.right ?? ""}</span><span class="k-chev" aria-hidden="true"></span></a>`;
|
|
348
|
+
}
|
|
349
|
+
/** A list of settings links under a section heading ("In this group"), or the overview's unlabelled list. */
|
|
350
|
+
export function settingsLinks(title, links, options = {}) {
|
|
351
|
+
return html `<section class="k-ssec"${title === null ? html ` aria-label="${options.label ?? "All settings"}"` : ""}>${title === null ? "" : html `<div class="k-secthead"><h3>${title}</h3></div>`}\
|
|
352
|
+
<div class="k-slinks">${links}</div></section>`;
|
|
353
|
+
}
|
|
354
|
+
/** A record row (a session, a token, a person, a device, a provider): title and one muted line · when · one action,
|
|
355
|
+
* and anything opened under it (a form, a refusal). */
|
|
356
|
+
export function settingsRecord(options) {
|
|
357
|
+
return html `<div class="k-srec"${attributes(options.attrs ?? {})}><div class="k-sl"><span class="k-sl-t">${options.title}</span>${options.sub ? html `<span class="k-sl-h">${options.sub}</span>` : ""}</div>\
|
|
358
|
+
<span class="k-srec-when">${options.when ?? ""}</span><span class="k-srec-a">${options.action ?? ""}</span>${options.more ? html `<div class="k-srec-more">${options.more}</div>` : ""}</div>`;
|
|
359
|
+
}
|
|
360
|
+
/** A command or path to copy by hand: monospace, selectable. */
|
|
361
|
+
export const codeLine = (text) => html `<code class="k-scode">${text}</code>`;
|
package/dist/server/pages.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ServerResponse } from "node:http";
|
|
2
2
|
import type { ServerRuntime } from './runtime.js';
|
|
3
|
+
import { type ProblemAt } from "./render-projects.js";
|
|
3
4
|
import { type Who } from "./session.js";
|
|
4
5
|
export declare function createPagesHandlers(runtime: ServerRuntime): {
|
|
5
6
|
registrations: import("./handler-registry.js").Registration[];
|
|
6
|
-
projectsScreen: (response: ServerResponse, who: Who, problem: string | null, status: number, returnTo?: string) => Promise<void>;
|
|
7
|
+
projectsScreen: (response: ServerResponse, who: Who, problem: string | null, status: number, returnTo?: string, problemAt?: ProblemAt) => Promise<void>;
|
|
7
8
|
};
|