toolroll 0.9.100 → 0.9.102
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 +15 -0
- package/dist/accent-colors.js +1 -1
- package/dist/activity-line.d.ts +1 -1
- package/dist/activity-line.js +1 -1
- package/dist/browser/terminal.css +1 -1
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +40 -40
- package/dist/browser-workspace.d.ts +78 -15
- package/dist/browser-workspace.js +5 -3
- package/dist/design/kit.css +94 -2
- package/dist/design/kit.d.ts +12 -4
- package/dist/design/kit.js +8 -2
- package/dist/design/tokens.css +5 -5
- package/dist/desktop-update.d.ts +1 -1
- package/dist/first-run.d.ts +10 -6
- package/dist/first-run.js +19 -18
- package/dist/guides.js +1 -1
- package/dist/operate.js +2 -0
- package/dist/page-work-contract.d.ts +21 -0
- package/dist/page-work-contract.js +5 -0
- package/dist/project-knowledge.d.ts +6 -6
- package/dist/run-efficiency.d.ts +3 -2
- package/dist/run-efficiency.js +4 -3
- package/dist/scope.d.ts +5 -0
- package/dist/scope.js +8 -1
- package/dist/serve.js +51 -9
- package/dist/server/chrome.d.ts +11 -2
- package/dist/server/chrome.js +10 -1
- package/dist/server/console.css +65 -21
- package/dist/server/home.d.ts +27 -14
- package/dist/server/home.js +78 -63
- package/dist/server/http.d.ts +6 -0
- package/dist/server/http.js +8 -0
- package/dist/server/in-place.d.ts +15 -0
- package/dist/server/in-place.js +32 -0
- package/dist/server/kit.d.ts +36 -3
- package/dist/server/kit.js +33 -4
- package/dist/server/pages.js +23 -2
- package/dist/server/render-chat.d.ts +2 -0
- package/dist/server/render-chat.js +1 -0
- package/dist/server/render-pages.js +1 -1
- package/dist/server/render-results.js +3 -3
- package/dist/server/render-tasks.d.ts +10 -0
- package/dist/server/render-tasks.js +245 -65
- package/dist/server/request-context.d.ts +14 -0
- package/dist/server/request-context.js +3 -0
- package/dist/server/task-views.js +11 -4
- package/dist/server/tasks.d.ts +8 -2
- package/dist/server/tasks.js +31 -17
- package/dist/settings-groups-ui.js +1 -1
- package/dist/store.js +14 -3
- package/dist/task-archive.d.ts +3 -0
- package/dist/task-archive.js +5 -0
- package/dist/task-status.d.ts +2 -2
- package/dist/task-status.js +3 -3
- package/dist/tui.js +1 -1
- package/dist/work-index.d.ts +14 -1
- package/dist/work-index.js +24 -8
- package/dist/workspace-ui.d.ts +69 -0
- package/dist/workspace-ui.js +103 -0
- package/package.json +1 -1
|
@@ -22,18 +22,19 @@ export type KnowledgeView = ReturnType<typeof knowledgeView>;
|
|
|
22
22
|
export declare function knowledgeVersion(store: Store, repo: string, actor: string, revision: number): Knowledge;
|
|
23
23
|
/** Chat reads only an admitted project. Keep the full source in the project UI. */
|
|
24
24
|
export declare function conversationKnowledge(store: Store, repo: string, actor: string, reference?: string, decision?: number): {
|
|
25
|
+
title?: never;
|
|
26
|
+
content?: never;
|
|
25
27
|
history?: never;
|
|
26
28
|
revision: number;
|
|
27
29
|
stale: boolean;
|
|
28
30
|
notice: string;
|
|
29
|
-
title?: never;
|
|
30
|
-
content?: never;
|
|
31
31
|
instructions?: never;
|
|
32
32
|
references?: never;
|
|
33
33
|
decisions?: never;
|
|
34
34
|
waiting?: never;
|
|
35
35
|
} | {
|
|
36
|
-
|
|
36
|
+
title?: never;
|
|
37
|
+
content?: never;
|
|
37
38
|
history?: never;
|
|
38
39
|
stale?: never;
|
|
39
40
|
decision: {
|
|
@@ -52,8 +53,7 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
|
|
|
52
53
|
};
|
|
53
54
|
afterwards?: string;
|
|
54
55
|
notice: string;
|
|
55
|
-
|
|
56
|
-
content?: never;
|
|
56
|
+
revision?: never;
|
|
57
57
|
instructions?: never;
|
|
58
58
|
references?: never;
|
|
59
59
|
decisions?: never;
|
|
@@ -70,9 +70,9 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
|
|
|
70
70
|
decisions?: never;
|
|
71
71
|
waiting?: never;
|
|
72
72
|
} | {
|
|
73
|
-
stale?: never;
|
|
74
73
|
title?: never;
|
|
75
74
|
content?: never;
|
|
75
|
+
stale?: never;
|
|
76
76
|
revision: number;
|
|
77
77
|
history: {
|
|
78
78
|
revision: number;
|
package/dist/run-efficiency.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ export declare function deriveRunEfficiency(run: Run & {
|
|
|
68
68
|
}, evidenceRoot: string, now: Date, commands?: boolean): RunEfficiency;
|
|
69
69
|
/**
|
|
70
70
|
* A finished run's How it ran: the saved one when it was derived by this version, else derived now and saved (once:
|
|
71
|
-
* a row whose steps were complete or have expired is final). An unfinished run is derived but never saved
|
|
71
|
+
* a row whose steps were complete or have expired is final). An unfinished run is derived but never saved, and
|
|
72
|
+
* nothing is saved when `save` is false (a page read ahead of its link writes nothing).
|
|
72
73
|
*/
|
|
73
|
-
export declare function runEfficiencyOf(store: Store, evidenceRoot: string, runId: number, now: Date): RunEfficiency | null;
|
|
74
|
+
export declare function runEfficiencyOf(store: Store, evidenceRoot: string, runId: number, now: Date, save?: boolean): RunEfficiency | null;
|
package/dist/run-efficiency.js
CHANGED
|
@@ -199,9 +199,10 @@ export function deriveRunEfficiency(run, evidenceRoot, now, commands = false) {
|
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
201
201
|
* A finished run's How it ran: the saved one when it was derived by this version, else derived now and saved (once:
|
|
202
|
-
* a row whose steps were complete or have expired is final). An unfinished run is derived but never saved
|
|
202
|
+
* a row whose steps were complete or have expired is final). An unfinished run is derived but never saved, and
|
|
203
|
+
* nothing is saved when `save` is false (a page read ahead of its link writes nothing).
|
|
203
204
|
*/
|
|
204
|
-
export function runEfficiencyOf(store, evidenceRoot, runId, now) {
|
|
205
|
+
export function runEfficiencyOf(store, evidenceRoot, runId, now, save = true) {
|
|
205
206
|
const run = store.getRun(runId);
|
|
206
207
|
if (run === null)
|
|
207
208
|
return null;
|
|
@@ -227,7 +228,7 @@ export function runEfficiencyOf(store, evidenceRoot, runId, now) {
|
|
|
227
228
|
const payload = JSON.stringify(derived);
|
|
228
229
|
// A row too large for its column (it can't be: the counts are bounded) is shown, never saved.
|
|
229
230
|
// Saving is a convenience: a store that can't take the row (read-only, busy) still shows what was derived.
|
|
230
|
-
if (payload.length <= 16_384) {
|
|
231
|
+
if (save && payload.length <= 16_384) {
|
|
231
232
|
try {
|
|
232
233
|
store.handle.prepare(`INSERT INTO run_efficiency (run, parser_version, steps, steps_reason, payload_json, derived_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
233
234
|
ON CONFLICT(run) DO UPDATE SET parser_version = excluded.parser_version, steps = excluded.steps, steps_reason = excluded.steps_reason,
|
package/dist/scope.d.ts
CHANGED
|
@@ -605,6 +605,11 @@ export type ApproveResult = {
|
|
|
605
605
|
| {
|
|
606
606
|
ok: false;
|
|
607
607
|
reason: "archived";
|
|
608
|
+
}
|
|
609
|
+
/** The task was cancelled: a cancelled task takes no approval, by any road (a stale page, the CLI). */
|
|
610
|
+
| {
|
|
611
|
+
ok: false;
|
|
612
|
+
reason: "cancelled";
|
|
608
613
|
};
|
|
609
614
|
/**
|
|
610
615
|
* Whether this name-and-token pair is a person the store knows. Shared by
|
package/dist/scope.js
CHANGED
|
@@ -52,7 +52,7 @@ import { z } from "zod";
|
|
|
52
52
|
import {} from "./contracts/acceptance-terms.js";
|
|
53
53
|
import { parseContract } from "./contracts/contract.js";
|
|
54
54
|
import { readAcceptance, readScopeTerms, storedRubricSchema } from "./contracts/scope.js";
|
|
55
|
-
import { taskArchived } from "./task-archive.js";
|
|
55
|
+
import { taskArchived, taskCancelled } from "./task-archive.js";
|
|
56
56
|
import { releaseCiHoldOf, releaseCiHoldWords } from "./release-ci-hold.js";
|
|
57
57
|
import { NO_READINESS, exactModelId, legOf, projectRoute, routeDigestOf, routeFromJson, routeJsonProblem, routeWords, } from "./phase-routing.js";
|
|
58
58
|
/** Same shape as a runner's credential, for the same reasons. */
|
|
@@ -1120,6 +1120,10 @@ chat) {
|
|
|
1120
1120
|
// A put-away task (v145) is restored first; sealScopeApproval refuses it too, on every road.
|
|
1121
1121
|
if (taskArchived(store, taskId))
|
|
1122
1122
|
return { ok: false, reason: "archived" };
|
|
1123
|
+
// A cancelled task takes no yes either — read here, in the seal's transaction, before any vote or proof is
|
|
1124
|
+
// written: an approval page left open while the task was cancelled elsewhere, or the CLI, approves nothing.
|
|
1125
|
+
if (taskCancelled(store, taskId))
|
|
1126
|
+
return { ok: false, reason: "cancelled" };
|
|
1123
1127
|
// The digest is required, not optional. An operator reads a scope, somebody
|
|
1124
1128
|
// rewrites it, and an approval that did not name what it saw would agree to
|
|
1125
1129
|
// the new one in silence — which is the whole failure this guards.
|
|
@@ -1550,6 +1554,9 @@ export function liveReleaseOf(store, taskId, action, now = new Date()) {
|
|
|
1550
1554
|
// A put-away task (v145, task-tidy.md), or a put-away anchor, deploys, merges and carries nothing: restore it first.
|
|
1551
1555
|
if (taskArchived(store, taskId) || taskArchived(store, a.anchorTask))
|
|
1552
1556
|
return refuse("the release task is put away");
|
|
1557
|
+
// A cancelled task, or a cancelled anchor, the same: cancelling ends what its approval allows.
|
|
1558
|
+
if (taskCancelled(store, taskId) || taskCancelled(store, a.anchorTask))
|
|
1559
|
+
return refuse("the release task was cancelled");
|
|
1553
1560
|
const newest = newestReleaseGeneration(store, a.repo, a.version, a.pr);
|
|
1554
1561
|
if (newest === null || newest.id !== a.id)
|
|
1555
1562
|
return refuse("a newer approval of this release replaced this one");
|
package/dist/serve.js
CHANGED
|
@@ -27,6 +27,7 @@ const PROXY_HEADER = /^(forwarded|via|x-forwarded-.+|x-real-ip|x-client-ip|x-ori
|
|
|
27
27
|
const BACKGROUND_CHECKS_MS = 30_000;
|
|
28
28
|
const LOCAL_AGENTS_EVERY_MS = 2 * 60_000;
|
|
29
29
|
import { browserCrewFromIndex, browserCrewOf, browserNavigationOf, browserProjectsOf, needsYouLabelOf } from './browser-workspace.js';
|
|
30
|
+
import { PAGE_WORK_HEADER, PAGE_WORK_VERSION } from './page-work-contract.js';
|
|
30
31
|
import { sharedActionPayload } from './chat-actions.js';
|
|
31
32
|
import { prepareCodingContext } from './coding-context.js';
|
|
32
33
|
import { CodingWorkspace } from './coding-workspace.js';
|
|
@@ -155,7 +156,8 @@ import { createFlowsHandlers } from './server/flows.js';
|
|
|
155
156
|
import { createPagesHandlers } from './server/pages.js';
|
|
156
157
|
import { createPeopleHandlers } from './server/people-tokens.js';
|
|
157
158
|
import { createSettingsHandlers } from './server/settings.js';
|
|
158
|
-
import { requestContext } from "./server/request-context.js";
|
|
159
|
+
import { readPurpose, requestContext } from "./server/request-context.js";
|
|
160
|
+
import { inPlaceRoute, prefetchRoute } from "./server/in-place.js";
|
|
159
161
|
import { asLocalOwner, localModeOn, localOwnerRefusal, localOwnerStandsNow } from "./local-owner.js";
|
|
160
162
|
import { defaultAgentOf, fillDefaultRouting } from "./model-choices.js";
|
|
161
163
|
import { chromeScript, DEMO_BANNER, DEMO_BANNER_SHORT, focusDocument, KBD_HELP, pinnedTheme, QUEUE_VIEW, refuse, screen, SENSITIVE_INPUT, shell, sidebarScript } from "./server/chrome.js";
|
|
@@ -878,9 +880,22 @@ export function createDecisionServer(options) {
|
|
|
878
880
|
// Passive polls authenticate without extending the browser session.
|
|
879
881
|
const fragmentName = url.searchParams.get("fragment");
|
|
880
882
|
const workspaceRead = method === "GET" && url.searchParams.get('format') === 'workspace';
|
|
881
|
-
|
|
883
|
+
// A workspace read's purpose (live-pages-and-home.md A1.4): bookkeeping only. Read on workspace GETs alone; on any other
|
|
884
|
+
// request the header means nothing. No authorisation decision below reads it.
|
|
885
|
+
const purpose = workspaceRead ? readPurpose(request.headers['x-toolroll-read']) : 'poll';
|
|
886
|
+
// The reads that never extend the session, whatever their header says.
|
|
887
|
+
const noTouch = edgeRoute?.domain === 'live' || (fragmentName !== null && NO_TOUCH_FRAGMENTS.has(fragmentName)) || /^\/code\/[a-f0-9]{32}\/state$/.test(url.pathname) || url.pathname === '/term/state'
|
|
882
888
|
// The Agents view's Live grid only watches: its polls never extend or write the session.
|
|
883
|
-
|| url.pathname === '/agents/live' || /^\/agents\/live\/[0-9]{1,15}$/.test(url.pathname) || (url.pathname === '/agents' && url.searchParams.get('view') === 'live')
|
|
889
|
+
|| url.pathname === '/agents/live' || /^\/agents\/live\/[0-9]{1,15}$/.test(url.pathname) || (url.pathname === '/agents' && url.searchParams.get('view') === 'live');
|
|
890
|
+
// A followed link (`navigate`) extends the session exactly as the HTML GET of the same address would; every other
|
|
891
|
+
// workspace read (a re-read, a prefetch) does not.
|
|
892
|
+
const touch = !(method === "GET" && (noTouch || (workspaceRead && purpose !== 'navigate')));
|
|
893
|
+
// A read ahead is answered only for a browser session, only for the prefetch allowlist (in-place.ts): anything else
|
|
894
|
+
// hears 204 before any sign-in bookkeeping (a token's last use, a sign-in charge) is written.
|
|
895
|
+
if (purpose === 'prefetch' && (request.headers.authorization !== undefined || !prefetchRoute(matchedRoute?.id, url))) {
|
|
896
|
+
request.resume();
|
|
897
|
+
return respond(response, 204, "text/plain; charset=utf-8", "");
|
|
898
|
+
}
|
|
884
899
|
const refuseBudget = (admitted) => {
|
|
885
900
|
if (admitted.ok)
|
|
886
901
|
return false;
|
|
@@ -906,6 +921,11 @@ export function createDecisionServer(options) {
|
|
|
906
921
|
}
|
|
907
922
|
if (who?.via === "bearer" && refuseBudget(admitBearer(request, who)))
|
|
908
923
|
return;
|
|
924
|
+
// A read ahead from anything but a signed-in browser session (signed out, a token): 204, never the sign-in page.
|
|
925
|
+
if (purpose === 'prefetch' && who?.via !== 'cookie') {
|
|
926
|
+
request.resume();
|
|
927
|
+
return respond(response, 204, "text/plain; charset=utf-8", "");
|
|
928
|
+
}
|
|
909
929
|
const passwordProof = who?.via === 'cookie' ? { name: who.name, generation: who.session.generation } : null;
|
|
910
930
|
const fresh = freshFactsOf(request, who, matchedRoute?.freshness === 'allowlist');
|
|
911
931
|
if (edgeRoute?.domain === 'people')
|
|
@@ -940,6 +960,9 @@ export function createDecisionServer(options) {
|
|
|
940
960
|
get fresh() { return freshFacts.getStore() != null && ssoNowAvailable(who.name); } } : undefined,
|
|
941
961
|
refusal: (answer, status, body) => sendScreen(answer, status, screen(status === 404 ? "Not found" : "Request refused", body, { chrome: chromeFor(who.via === "cookie" ? who.session.project : null, "work") })),
|
|
942
962
|
workspaceRead,
|
|
963
|
+
readPurpose: purpose,
|
|
964
|
+
...(workspaceRead && request.headers[PAGE_WORK_HEADER] === String(PAGE_WORK_VERSION) ? { appWork: true } : {}),
|
|
965
|
+
...(matchedRoute?.stage === 'console' ? { routeId: matchedRoute.id } : {}),
|
|
943
966
|
freshWork: ["request", "task", "result"].some(name => url.searchParams.has(name)),
|
|
944
967
|
...(workspaceRead && typeof request.headers['if-none-match'] === 'string' ? { workspaceIfNoneMatch: request.headers['if-none-match'] } : {}),
|
|
945
968
|
workspaceRequest: url.searchParams.get('request'),
|
|
@@ -952,7 +975,9 @@ export function createDecisionServer(options) {
|
|
|
952
975
|
? { localStepUp: localOwnerRefusal(false, method === "GET" ? passwordSetNext(pagePathOf(url)) : "/").href } : {}),
|
|
953
976
|
reconnect: browserKeys.stale(who, method === "GET" ? safeReturn(pagePathOf(url)) : "/"),
|
|
954
977
|
};
|
|
955
|
-
|
|
978
|
+
// A read ahead writes nothing, not even a slow request's record.
|
|
979
|
+
if (purpose !== 'prefetch')
|
|
980
|
+
response.once("finish", () => keepSlowRequest(store, timeline, matchedRoute?.id ?? "unmatched", response.statusCode, clock()));
|
|
956
981
|
if (method === "GET" || method === "POST")
|
|
957
982
|
return freshFacts.run(fresh, () => provenPasswordAccount.run(passwordProof, () => requestContext.run(requestFacts, async () => {
|
|
958
983
|
if (edgeRoute?.domain === 'live')
|
|
@@ -1669,8 +1694,29 @@ export function createDecisionServer(options) {
|
|
|
1669
1694
|
}
|
|
1670
1695
|
const conversation = extras.conversation ?? null;
|
|
1671
1696
|
const request = requestFacts.workspaceRequest;
|
|
1697
|
+
// React owns the conversation and its refresh/draft lifecycle. Native
|
|
1698
|
+
// guarded forms keep their current behavior after React inserts them.
|
|
1699
|
+
// A page with its own view keeps its script; the chat page runs the result script.
|
|
1700
|
+
// The exact page script the document would run: the JSON arm and the document arm read this one value.
|
|
1701
|
+
const ownScript = conversation === null || extras.view;
|
|
1702
|
+
const pageScript = ownScript ? (s.functional?.script ?? '') : RESULT_REVIEW_SCRIPT;
|
|
1703
|
+
const routeInPlace = inPlaceRoute(requestFacts.routeId, path);
|
|
1704
|
+
// The app runs the page's behaviour itself (docs/plans/task-page-in-app.md §2) only when all of these hold: the
|
|
1705
|
+
// app covers the WHOLE script, no password field (a password page keeps its string path), an in-place route, and a
|
|
1706
|
+
// reader that can run it: a document load (its bundle is this server's, served no-store) or a workspace read that
|
|
1707
|
+
// says so. Anything else is the page exactly as before: the string, no work, a document.
|
|
1708
|
+
const covered = ownScript ? s.functional?.app : undefined;
|
|
1709
|
+
const work = pageScript !== '' && covered !== undefined && covered.length > 0 && !sensitive && routeInPlace
|
|
1710
|
+
&& (!requestFacts.workspaceRead || requestFacts.appWork === true) ? covered : null;
|
|
1711
|
+
const functional = work === null ? pageScript : '';
|
|
1712
|
+
// May replace another page without a document load (live-pages-and-home.md A1.1): an allowlisted route, no password
|
|
1713
|
+
// field, no page script (a page script runs once per document, so its page always loads as one).
|
|
1714
|
+
const inPlace = routeInPlace && !sensitive && functional === '';
|
|
1672
1715
|
const workspace = {
|
|
1673
|
-
version: 1, path: currentPath, title: s.title, user: requestFacts.actor ?? '', csrf: requestFacts.csrf, sensitive,
|
|
1716
|
+
version: 1, path: currentPath, title: s.title, user: requestFacts.actor ?? '', csrf: requestFacts.csrf, sensitive, inPlace,
|
|
1717
|
+
...(work === null ? {} : { work }),
|
|
1718
|
+
// Said on the document that ships it: this document's page script listens for as long as the document lives.
|
|
1719
|
+
...(functional !== '' && !requestFacts.workspaceRead ? { scripted: true } : {}),
|
|
1674
1720
|
// The project picker names the open project; the settings frame carries its breadcrumb.
|
|
1675
1721
|
// Only a project this person may see now: never the raw saved path (settings-kit.ts openProjectFor).
|
|
1676
1722
|
openProject: openProjectFor(requestFacts.openProject ?? null, s.chrome.project, s.chrome.projects ?? []),
|
|
@@ -1761,10 +1807,6 @@ export function createDecisionServer(options) {
|
|
|
1761
1807
|
return respond(response, status, 'application/json; charset=utf-8', body);
|
|
1762
1808
|
}
|
|
1763
1809
|
const nonce = randomBytes(16).toString('base64');
|
|
1764
|
-
// React owns the conversation and its refresh/draft lifecycle. Native
|
|
1765
|
-
// guarded forms keep their current behavior after React inserts them.
|
|
1766
|
-
// A page with its own view keeps its script; the chat page runs the result script.
|
|
1767
|
-
const functional = conversation === null || extras.view ? (s.functional?.script ?? '') : RESULT_REVIEW_SCRIPT;
|
|
1768
1810
|
const document = shell(s.title, s.body, { chrome: s.chrome, sensitive });
|
|
1769
1811
|
// The keys and their list (keymap.ts) work here as on every chromed page; the list sits beside the app React owns.
|
|
1770
1812
|
// A sensitive page keeps to its own script.
|
package/dist/server/chrome.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BrowserSignIn, type BrowserUpdateNotice, type BrowserWorkspace } from "../browser-workspace.js";
|
|
1
|
+
import { type BrowserSignIn, type BrowserUpdateNotice, type BrowserWorkspace, type PageWork } from "../browser-workspace.js";
|
|
2
2
|
import { type Crumb } from "../settings-kit.js";
|
|
3
3
|
import { type Decision, type Run } from "../store.js";
|
|
4
4
|
import { type tally } from "../summary.js";
|
|
@@ -56,7 +56,7 @@ export declare const DEMO_BANNER_SHORT = "Demo: sample projects. Nothing calls a
|
|
|
56
56
|
* a person's accent (accentHead) still wins over the generated default. */
|
|
57
57
|
export declare const STYLE: string;
|
|
58
58
|
/** Appearance: a three-way segmented switch, one tap per choice. */
|
|
59
|
-
export declare const THEME_CONTROLS_CSS = ".task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-
|
|
59
|
+
export declare const THEME_CONTROLS_CSS = ".task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-neutral-soft);color:var(--so-ink)}.verdict-chip--info{background:var(--so-info-soft);color:var(--so-info)}.verdict-by{margin:.4rem 0 0}details.result-request-open{margin:.5rem 0}details.result-request-open>summary{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;cursor:pointer;list-style:none}details.result-request-open>summary::-webkit-details-marker{display:none}details.result-request-open[open]>summary{margin-bottom:.75rem}.settings-tiles{display:grid;gap:1.25rem;margin:0 0 2rem}.settings-tiles h2{margin:0 0 .5rem;font-size:.875rem;font-weight:600;color:var(--so-muted)}.settings-tiles section>div{display:grid;grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr));gap:.5rem}.settings-tiles a>span{display:grid;gap:.1rem;min-width:0}.settings-tiles .provider-status{font-weight:400;font-size:.8125rem}.settings-tiles a{display:flex;align-items:center;gap:.6rem;min-height:3rem;padding:.65rem .8rem;border:1px solid var(--so-line);border-radius:.625rem;background:var(--so-paper);color:var(--so-ink);text-decoration:none;font-weight:550;font-size:.875rem}@media(hover:hover) and (pointer:fine){.settings-tiles a:hover{border-color:var(--so-input-line);background:var(--so-raised)}}.settings-tiles a>svg{width:1.1rem;height:1.1rem;flex-shrink:0;color:var(--so-accent-text)}details.settings-more{margin:.25rem 0 1.25rem}details.settings-more>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:550}details.settings-more>summary .meta{font-weight:400;margin-left:.35rem}.settings-changed{margin-top:-.25rem}.appearance{margin:0 0 28px}.appearance h2{margin:0 0 10px}.theme-switch{display:inline-flex;flex-wrap:nowrap;max-width:100%;gap:4px;padding:4px;margin:0;border:1px solid var(--so-line);border-radius:10px;background:var(--so-raised)}.theme-switch .theme-choice,.so-native-region .theme-switch .theme-choice{flex:1 1 0;width:auto;white-space:nowrap;min-height:40px;padding:8px 16px;border:0;border-radius:7px;background:transparent;color:var(--so-muted);font:inherit;font-weight:550;box-shadow:none;cursor:pointer}@media(hover:hover) and (pointer:fine){.theme-switch .theme-choice:hover{color:var(--so-ink)}}.theme-switch .theme-choice[aria-pressed=\"true\"]{background:var(--so-paper);color:var(--so-ink);box-shadow:0 1px 2px rgb(0 0 0 / .1)}.appearance .meta{margin:8px 0 0}@media(max-width:600px){.theme-switch .theme-choice{min-height:44px}}.update-notes{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;max-height:18rem;overflow:auto}";
|
|
60
60
|
/** The page CSS this module writes itself (not the imported modules'), for the stylesheet contract tests. */
|
|
61
61
|
export declare const PAGE_CSS: string;
|
|
62
62
|
/** The Inbox tabs (console v2): a segmented track of real links; the dot marks a tab with something new, on phones only. */
|
|
@@ -176,6 +176,9 @@ export type Screen = {
|
|
|
176
176
|
functional?: {
|
|
177
177
|
script: string;
|
|
178
178
|
fetches?: boolean;
|
|
179
|
+
/** The app's own version of this WHOLE script (task-page-in-app.md §1): where the app can run it, the document
|
|
180
|
+
* ships no script string and the page may change in place. Added to only through withScript. */
|
|
181
|
+
app?: readonly PageWork[];
|
|
179
182
|
};
|
|
180
183
|
refreshSeconds?: number;
|
|
181
184
|
/** Render sensitive even when no password field is visible — one-time
|
|
@@ -317,3 +320,9 @@ export declare function oneLineOf(text: string, cap: number): string;
|
|
|
317
320
|
* minutes are the answer without script.
|
|
318
321
|
*/
|
|
319
322
|
export declare function boardTickScript(regionId?: string): string;
|
|
323
|
+
/**
|
|
324
|
+
* A page's script with one more added (docs/plans/task-page-in-app.md §1). All or nothing: `app` survives only when
|
|
325
|
+
* every part has it, so a script the app does not cover keeps the whole page a document, and a page never gets half
|
|
326
|
+
* its behaviour from the string and half from the app.
|
|
327
|
+
*/
|
|
328
|
+
export declare function withScript(base: Screen["functional"], add: NonNullable<Screen["functional"]>): NonNullable<Screen["functional"]>;
|
package/dist/server/chrome.js
CHANGED
|
@@ -150,7 +150,7 @@ export const STYLE = readFileSync(join(import.meta.dirname, "..", "design", "tok
|
|
|
150
150
|
// server pages (server/kit.ts) and React views (browser/kit/) looks the same everywhere.
|
|
151
151
|
readFileSync(join(import.meta.dirname, "..", "design", "kit.css"), "utf8");
|
|
152
152
|
/** Appearance: a three-way segmented switch, one tap per choice. */
|
|
153
|
-
export const THEME_CONTROLS_CSS = `.task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-
|
|
153
|
+
export const THEME_CONTROLS_CSS = `.task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-neutral-soft);color:var(--so-ink)}.verdict-chip--info{background:var(--so-info-soft);color:var(--so-info)}.verdict-by{margin:.4rem 0 0}details.result-request-open{margin:.5rem 0}details.result-request-open>summary{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;cursor:pointer;list-style:none}details.result-request-open>summary::-webkit-details-marker{display:none}details.result-request-open[open]>summary{margin-bottom:.75rem}.settings-tiles{display:grid;gap:1.25rem;margin:0 0 2rem}.settings-tiles h2{margin:0 0 .5rem;font-size:.875rem;font-weight:600;color:var(--so-muted)}.settings-tiles section>div{display:grid;grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr));gap:.5rem}.settings-tiles a>span{display:grid;gap:.1rem;min-width:0}.settings-tiles .provider-status{font-weight:400;font-size:.8125rem}.settings-tiles a{display:flex;align-items:center;gap:.6rem;min-height:3rem;padding:.65rem .8rem;border:1px solid var(--so-line);border-radius:.625rem;background:var(--so-paper);color:var(--so-ink);text-decoration:none;font-weight:550;font-size:.875rem}@media(hover:hover) and (pointer:fine){.settings-tiles a:hover{border-color:var(--so-input-line);background:var(--so-raised)}}.settings-tiles a>svg{width:1.1rem;height:1.1rem;flex-shrink:0;color:var(--so-accent-text)}details.settings-more{margin:.25rem 0 1.25rem}details.settings-more>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:550}details.settings-more>summary .meta{font-weight:400;margin-left:.35rem}.settings-changed{margin-top:-.25rem}.appearance{margin:0 0 28px}.appearance h2{margin:0 0 10px}.theme-switch{display:inline-flex;flex-wrap:nowrap;max-width:100%;gap:4px;padding:4px;margin:0;border:1px solid var(--so-line);border-radius:10px;background:var(--so-raised)}.theme-switch .theme-choice,.so-native-region .theme-switch .theme-choice{flex:1 1 0;width:auto;white-space:nowrap;min-height:40px;padding:8px 16px;border:0;border-radius:7px;background:transparent;color:var(--so-muted);font:inherit;font-weight:550;box-shadow:none;cursor:pointer}@media(hover:hover) and (pointer:fine){.theme-switch .theme-choice:hover{color:var(--so-ink)}}.theme-switch .theme-choice[aria-pressed="true"]{background:var(--so-paper);color:var(--so-ink);box-shadow:0 1px 2px rgb(0 0 0 / .1)}.appearance .meta{margin:8px 0 0}@media(max-width:600px){.theme-switch .theme-choice{min-height:44px}}.update-notes{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;max-height:18rem;overflow:auto}`;
|
|
154
154
|
/** The page CSS this module writes itself (not the imported modules'), for the stylesheet contract tests. */
|
|
155
155
|
export const PAGE_CSS = STYLE + THEME_CONTROLS_CSS;
|
|
156
156
|
/** The Inbox tabs (console v2): a segmented track of real links; the dot marks a tab with something new, on phones only. */
|
|
@@ -807,3 +807,12 @@ export function boardTickScript(regionId = "board-region") {
|
|
|
807
807
|
`run();setInterval(function(){if(!document.hidden)run();},1000);document.addEventListener("visibilitychange",function(){if(!document.hidden)run();});` +
|
|
808
808
|
`if(region&&typeof MutationObserver==="function")new MutationObserver(run).observe(region,{childList:true});})();`);
|
|
809
809
|
}
|
|
810
|
+
/**
|
|
811
|
+
* A page's script with one more added (docs/plans/task-page-in-app.md §1). All or nothing: `app` survives only when
|
|
812
|
+
* every part has it, so a script the app does not cover keeps the whole page a document, and a page never gets half
|
|
813
|
+
* its behaviour from the string and half from the app.
|
|
814
|
+
*/
|
|
815
|
+
export function withScript(base, add) {
|
|
816
|
+
const app = base === undefined ? add.app : base.app !== undefined && add.app !== undefined ? [...base.app, ...add.app] : undefined;
|
|
817
|
+
return { script: (base?.script ?? "") + add.script, fetches: (base?.fetches ?? false) || (add.fetches ?? false), ...(app === undefined ? {} : { app }) };
|
|
818
|
+
}
|
package/dist/server/console.css
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
--muted: var(--so-raised); --muted-foreground: var(--so-muted); --border: var(--so-line); --input: var(--so-input-line);
|
|
42
42
|
--primary: var(--so-accent); --primary-foreground: var(--so-on-accent); --secondary: var(--so-raised);
|
|
43
43
|
--accent: var(--so-soft); --destructive: var(--so-danger); --destructive-soft: var(--so-danger-soft);
|
|
44
|
-
--success: var(--so-success); --success-soft: var(--so-success-soft); --warning: var(--so-
|
|
44
|
+
--success: var(--so-success); --success-soft: var(--so-success-soft); --warning: var(--so-neutral-ink); --warning-soft: var(--so-neutral-soft);
|
|
45
45
|
--running: var(--so-info); --running-soft: var(--so-info-soft); --ring: var(--so-signal);
|
|
46
46
|
/* The accent means one thing: a person is needed (approve, answer, decide). */
|
|
47
47
|
--brand: var(--so-attention); --brand-foreground: var(--so-on-attention); --brand-soft: var(--so-attention-soft);
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
}
|
|
142
142
|
.badge-note {
|
|
143
143
|
background: color-mix(in srgb, var(--muted) 58%, var(--card));
|
|
144
|
-
color: var(--so-
|
|
144
|
+
color: var(--so-neutral-ink); border-color: var(--border);
|
|
145
145
|
}
|
|
146
146
|
.badge-overdue {
|
|
147
147
|
background: color-mix(in srgb, var(--muted) 58%, var(--card)); color: var(--foreground);
|
|
@@ -173,17 +173,17 @@
|
|
|
173
173
|
}
|
|
174
174
|
.badge-cut { background: var(--muted); }
|
|
175
175
|
/* The status chip (components/ui/badge.tsx `size="chip"`, after ReUI's light badge with a dot): 20px, 6px
|
|
176
|
-
corners, a soft wash, 11.5px words and a 6px dot. Needs you is
|
|
177
|
-
|
|
176
|
+
corners, a soft wash, 11.5px words and a 6px dot. Needs you is solid ink with paper words and
|
|
177
|
+
no dot; elsewhere the dot is decoration and the word carries the meaning. */
|
|
178
178
|
.chip {
|
|
179
179
|
display: inline-flex; align-items: center; gap: .375rem; height: 1.25rem; padding: 0 .375rem; border-radius: var(--so-radius-sm);
|
|
180
180
|
font-family: var(--font-sans); font-size: .71875rem; font-weight: 500; line-height: 1; white-space: nowrap; vertical-align: middle;
|
|
181
181
|
background: var(--so-neutral-soft); color: var(--so-ink);
|
|
182
182
|
}
|
|
183
|
-
/* Ink words, the tone in the dot (the Agents → Live chip); Needs you
|
|
183
|
+
/* Ink words, the tone in the dot (the Agents → Live chip); Needs you is solid ink, no dot. */
|
|
184
184
|
.chip-dot { width: .375rem; height: .375rem; flex-shrink: 0; border-radius: var(--so-radius-full); background: var(--so-neutral-ink); }
|
|
185
|
-
.chip-needs { background: var(--so-
|
|
186
|
-
.chip-needs .chip-dot {
|
|
185
|
+
.chip-needs { background: var(--so-needs); color: var(--so-on-signal); }
|
|
186
|
+
.chip-needs .chip-dot { display: none; }
|
|
187
187
|
.chip-live { background: var(--so-info-soft); }
|
|
188
188
|
.chip-live .chip-dot { background: var(--so-info); }
|
|
189
189
|
.chip-done { background: var(--so-success-soft); }
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
.frame { display: flex; flex-direction: column; gap: .25rem; padding: .25rem; border: 1px solid var(--so-line); border-radius: var(--so-radius-card); background: var(--so-raised); }
|
|
196
196
|
.frame-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; min-height: 2rem; padding: .25rem .625rem; }
|
|
197
197
|
.frame-panel { position: relative; border: 1px solid var(--so-line); border-radius: calc(var(--so-radius-card) - .25rem - 1px); background: var(--so-paper); padding: .75rem .875rem; }
|
|
198
|
-
.frame-panel.waits { border-color: var(--so-
|
|
198
|
+
.frame-panel.waits { border-color: var(--so-needs); }
|
|
199
199
|
|
|
200
200
|
.card {
|
|
201
201
|
border: 1px solid var(--glass-border); border-radius: var(--radius); background: var(--glass);
|
|
@@ -1012,7 +1012,7 @@
|
|
|
1012
1012
|
.dispatch-status[data-dispatch-status="proof-refuted"] .dispatch-copy > strong::before,
|
|
1013
1013
|
.dispatch-status[data-dispatch-status="needs-verification"] .dispatch-copy > strong::before {
|
|
1014
1014
|
content: ""; display: inline-block; width: .45rem; height: .45rem; margin-right: .45rem; border-radius: 50%; vertical-align: .08rem;
|
|
1015
|
-
background: var(--
|
|
1015
|
+
background: var(--warning);
|
|
1016
1016
|
}
|
|
1017
1017
|
.problem.dispatch-status[data-dispatch-status="proof-refuted"] .dispatch-copy > strong::before { background: var(--destructive); }
|
|
1018
1018
|
.dispatch-status[data-dispatch-status="needs-verification"] .dispatch-copy > strong::before { background: var(--muted-foreground); }
|
|
@@ -1110,14 +1110,14 @@
|
|
|
1110
1110
|
.contract-panel { margin-top: .8rem; padding: .7rem .85rem; border: 1px solid var(--glass-border); border-radius: calc(var(--radius) - 3px); background: color-mix(in srgb, var(--muted) 45%, transparent); }
|
|
1111
1111
|
.contract-panel .approval-label { margin: 0 0 .25rem; }
|
|
1112
1112
|
.contract-panel p { margin: .2rem 0; }
|
|
1113
|
-
.contract-amended { border-color: color-mix(in srgb, var(--warning
|
|
1113
|
+
.contract-amended { border-color: color-mix(in srgb, var(--warning) 55%, var(--glass-border)); background: color-mix(in srgb, var(--warning) 9%, transparent); }
|
|
1114
1114
|
.contract-reason { font-size: .9rem; }
|
|
1115
1115
|
.contract-changes { margin: .5rem 0 0; padding-left: 1.1rem; display: grid; gap: .45rem; }
|
|
1116
1116
|
.contract-changes li { overflow-wrap: anywhere; }
|
|
1117
1117
|
.contract-change-kind { display: inline-block; margin-right: .3rem; padding: .05rem .4rem; border-radius: 999px; font: 600 .62rem/1.5 var(--font-mono); letter-spacing: .06em; text-transform: uppercase; background: color-mix(in srgb, var(--muted) 70%, transparent); }
|
|
1118
1118
|
.contract-change-added { background: color-mix(in srgb, var(--success, #2f9e5f) 18%, transparent); }
|
|
1119
1119
|
.contract-change-removed { background: color-mix(in srgb, var(--danger, #c8453d) 18%, transparent); }
|
|
1120
|
-
.contract-change-changed { background: color-mix(in srgb, var(--warning
|
|
1120
|
+
.contract-change-changed { background: color-mix(in srgb, var(--warning) 22%, transparent); }
|
|
1121
1121
|
.contract-before, .contract-after { margin-top: .15rem; font-size: .85rem; }
|
|
1122
1122
|
.contract-before { color: var(--muted-foreground); text-decoration: line-through; text-decoration-color: color-mix(in srgb, var(--muted-foreground) 55%, transparent); }
|
|
1123
1123
|
.contract-note { margin-top: .5rem; }
|
|
@@ -1251,6 +1251,35 @@
|
|
|
1251
1251
|
.approval-roles dd { margin: 0; }
|
|
1252
1252
|
.approval-roles dd ul { margin: 2px 0 0; color: var(--muted-foreground); }
|
|
1253
1253
|
.chat-plan .approval-sheet { padding: 0; border: 0; background: none; }
|
|
1254
|
+
/* The plan sheet (task page, docs/plans/task-page-steps.md): the same terms read top to bottom — one lead sentence,
|
|
1255
|
+
Steps, Done when, Will change / Won't touch, Worth knowing, the agent — every part open, small muted headings. */
|
|
1256
|
+
.plan-sheet { display: grid; gap: 22px; margin: 0; min-width: 0; color: var(--so-ink); }
|
|
1257
|
+
.plan-sheet > * { margin: 0; }
|
|
1258
|
+
.plan-sheet-lead { max-width: 66ch; font-size: 17px; line-height: 1.5; letter-spacing: -.01em; overflow-wrap: anywhere; }
|
|
1259
|
+
.plan-sheet-lead-long { font-size: 15px; line-height: 1.6; letter-spacing: 0; }
|
|
1260
|
+
.plan-sheet-text { max-width: 66ch; font-size: 14px; line-height: 1.65; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
1261
|
+
.plan-sheet-part { display: grid; align-content: start; gap: var(--so-space-sm); min-width: 0; max-width: 75ch; font-size: 14px; line-height: 1.6; overflow-wrap: anywhere; }
|
|
1262
|
+
.plan-sheet-part > *, .plan-sheet-part p, .plan-sheet-part ul, .plan-sheet-part ol, .plan-sheet-part dl { margin: 0; }
|
|
1263
|
+
.plan-sheet-h { margin: 0; font-size: 12px; font-weight: 500; line-height: 1.5; letter-spacing: 0; text-transform: none; color: var(--so-muted); }
|
|
1264
|
+
.plan-sheet-sub { color: var(--so-muted); font-size: 12.5px; }
|
|
1265
|
+
.plan-sheet-steps { display: grid; gap: var(--so-space-sm); padding: 0; list-style: none; counter-reset: plan-step; }
|
|
1266
|
+
.plan-sheet-steps > li { display: flex; gap: var(--so-space-md); counter-increment: plan-step; }
|
|
1267
|
+
.plan-sheet-steps > li::before { content: counter(plan-step); flex: 0 0 22px; box-sizing: border-box; width: 22px; height: 22px; border: 1px solid var(--so-line); border-radius: 50%; display: grid; place-items: center; font-size: 12px; color: var(--so-muted); font-variant-numeric: tabular-nums; }
|
|
1268
|
+
.plan-sheet-steps > li > span { min-width: 0; padding-top: 1px; }
|
|
1269
|
+
.plan-sheet-done { display: grid; padding: 0; list-style: none; }
|
|
1270
|
+
.plan-sheet-done > li { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--so-space-xs) var(--so-space-md); padding: var(--so-space-sm) 0; border-bottom: 1px solid var(--so-line); }
|
|
1271
|
+
.plan-sheet-done > li:last-child { border-bottom: 0; }
|
|
1272
|
+
.plan-sheet-done > li > span:first-child { flex: 1 1 260px; min-width: 0; }
|
|
1273
|
+
.plan-sheet-how { flex: none; font-size: 12.5px; color: var(--so-muted); }
|
|
1274
|
+
.plan-sheet-pair { display: flex; flex-wrap: wrap; gap: var(--so-space-lg) 40px; }
|
|
1275
|
+
.plan-sheet-pair > .plan-sheet-part { flex: 1 1 220px; }
|
|
1276
|
+
.plan-sheet .approval-paths li { color: var(--so-ink); font-size: 13px; line-height: 1.7; }
|
|
1277
|
+
.plan-sheet-list { display: grid; gap: var(--so-space-xs); padding-left: 1.25rem; }
|
|
1278
|
+
.plan-sheet-warn { color: var(--so-ink); }
|
|
1279
|
+
.plan-sheet .approval-amendment { margin: 0; }
|
|
1280
|
+
.plan-sheet .approval-who, .plan-sheet .approval-evidence, .plan-sheet .approval-allowing, .plan-sheet .approval-you-check { margin: 0; }
|
|
1281
|
+
.plan-sheet .approval-roles { margin: 0; }
|
|
1282
|
+
.plan-sheet .revision-card { margin: 0; padding: 0; border: 0; background: none; }
|
|
1254
1283
|
@media (max-width: 760px) {
|
|
1255
1284
|
.approval-sheet { gap: var(--so-space-md); padding: 14px; }
|
|
1256
1285
|
.approval-row { grid-template-columns: 1fr; gap: 2px; }
|
|
@@ -1564,7 +1593,7 @@
|
|
|
1564
1593
|
.status-line { display: inline-flex; align-items: center; gap: .45rem; min-width: 0; font-size: .875rem; font-weight: 600; color: var(--foreground); }
|
|
1565
1594
|
.status-line .status-label { min-width: 0; overflow-wrap: anywhere; }
|
|
1566
1595
|
.status-line .status-dot { flex: none; width: .5rem; height: .5rem; border-radius: 50%; background: var(--muted-foreground); }
|
|
1567
|
-
.status-line[data-tone="attention"] .status-dot { background: var(--
|
|
1596
|
+
.status-line[data-tone="attention"] .status-dot { background: var(--so-needs); }
|
|
1568
1597
|
.status-line[data-tone="problem"] .status-dot { background: var(--destructive); }
|
|
1569
1598
|
.status-line[data-tone="live"] .status-dot { background: var(--running); box-shadow: 0 0 0 3px var(--running-soft); }
|
|
1570
1599
|
.status-line[data-tone="ready"] .status-dot, .status-line[data-tone="done"] .status-dot { background: var(--success); }
|
|
@@ -1577,7 +1606,7 @@
|
|
|
1577
1606
|
.pull-request-head strong { font-size: 1.0625rem; font-weight: 600; color: var(--foreground); display: inline-flex; align-items: center; gap: .5rem; }
|
|
1578
1607
|
.pull-request-head strong::before { content: ""; width: .5rem; height: .5rem; border-radius: 999px; background: var(--muted-foreground); }
|
|
1579
1608
|
.pull-request--ok .pull-request-head strong::before { background: var(--success, #15803d); }
|
|
1580
|
-
.pull-request--problem .pull-request-head strong::before { background: var(--
|
|
1609
|
+
.pull-request--problem .pull-request-head strong::before { background: var(--foreground); }
|
|
1581
1610
|
.pull-request-act { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem .75rem; margin: .25rem 0 0; }
|
|
1582
1611
|
.pull-request-act .meta { flex: 1 1 100%; }
|
|
1583
1612
|
.pull-request-act label { display: grid; gap: .25rem; flex: 1 1 12rem; margin: 0; }
|
|
@@ -1813,7 +1842,7 @@
|
|
|
1813
1842
|
}
|
|
1814
1843
|
/* A lane is a frame (components/ui/frame.tsx, after ReUI's Frame): a raised well with a hairline, its head a
|
|
1815
1844
|
status chip and a mono count on the well's fill, its cards paper panels at concentric corners. Only the lane
|
|
1816
|
-
that waits on a person is
|
|
1845
|
+
that waits on a person is ink: its chip, and each card's thin full outline. No fill, no stripe, no shadow. */
|
|
1817
1846
|
.lane {
|
|
1818
1847
|
background: var(--so-raised); border: 1px solid var(--so-line);
|
|
1819
1848
|
border-radius: var(--so-radius-card); padding: .25rem; min-height: 12rem;
|
|
@@ -1830,7 +1859,7 @@
|
|
|
1830
1859
|
background: var(--so-paper); border: 1px solid var(--so-line);
|
|
1831
1860
|
border-radius: calc(var(--so-radius-card) - .25rem - 1px); padding: .625rem .75rem; margin-top: .25rem;
|
|
1832
1861
|
}
|
|
1833
|
-
.lane-attention .lane-card { border-color: var(--so-
|
|
1862
|
+
.lane-attention .lane-card { border-color: var(--so-needs); }
|
|
1834
1863
|
/* The order view's columns are frames too, with roomier insides (a form, a note) and a quiet dot by the name. */
|
|
1835
1864
|
.lane.queue-column { padding: .5rem; }
|
|
1836
1865
|
.queue-column > h2::before { content: ""; width: .375rem; height: .375rem; border-radius: var(--so-radius-full); flex: none; background: var(--muted-foreground); opacity: .55; }
|
|
@@ -2633,14 +2662,29 @@ button { min-height: 44px; }
|
|
|
2633
2662
|
the same on both, swaps in place instead of fading (each shell names its
|
|
2634
2663
|
own, so a page can never carry the name twice). */
|
|
2635
2664
|
::view-transition-old(root) { animation: so-page-leave 80ms ease-in both; }
|
|
2636
|
-
::view-transition-new(root) { animation: so-page-arrive
|
|
2665
|
+
::view-transition-new(root) { animation: so-page-arrive 160ms ease-out 60ms both; }
|
|
2637
2666
|
@keyframes so-page-leave { to { opacity: 0; } }
|
|
2638
2667
|
@keyframes so-page-arrive { from { opacity: 0; } }
|
|
2639
2668
|
.side { view-transition-name: so-nav-page; }
|
|
2640
2669
|
.so-sidebar { view-transition-name: so-nav-app; }
|
|
2641
|
-
|
|
2670
|
+
.so-ph { view-transition-name: so-phone-header; }
|
|
2671
|
+
::view-transition-group(so-nav-page), ::view-transition-group(so-nav-app), ::view-transition-group(so-phone-header), ::view-transition-group(so-message-box),
|
|
2642
2672
|
::view-transition-old(so-nav-page), ::view-transition-new(so-nav-page),
|
|
2643
|
-
::view-transition-old(so-nav-app), ::view-transition-new(so-nav-app)
|
|
2673
|
+
::view-transition-old(so-nav-app), ::view-transition-new(so-nav-app),
|
|
2674
|
+
::view-transition-old(so-phone-header), ::view-transition-new(so-phone-header),
|
|
2675
|
+
::view-transition-old(so-message-box), ::view-transition-new(so-message-box) { animation: none; }
|
|
2676
|
+
/* A page changed in place (docs/plans/live-pages-and-home.md A2.2, browser/transition.ts): only the page content
|
|
2677
|
+
moves; everything else (the root, the sidebar, the phone header, the message box) swaps where it stands. The same
|
|
2678
|
+
level fades through like a document load (80 ms out, 160 ms in); deeper, the new page also moves 8px in from the
|
|
2679
|
+
right over 200 ms; shallower, from the left. Nothing runs past 220 ms. */
|
|
2680
|
+
html.so-nav #workspace-main { view-transition-name: so-page; }
|
|
2681
|
+
html.so-nav::view-transition-old(root), html.so-nav::view-transition-new(root), html.so-nav::view-transition-group(so-page) { animation: none; }
|
|
2682
|
+
html.so-nav::view-transition-old(so-page) { animation: so-page-leave 80ms ease-in both; }
|
|
2683
|
+
html.so-nav-same::view-transition-new(so-page) { animation: so-page-arrive 160ms ease-out 60ms both; }
|
|
2684
|
+
html.so-nav-deeper::view-transition-new(so-page) { animation: so-page-deeper 200ms cubic-bezier(.2, .8, .2, 1) both; }
|
|
2685
|
+
html.so-nav-shallower::view-transition-new(so-page) { animation: so-page-shallower 200ms cubic-bezier(.2, .8, .2, 1) both; }
|
|
2686
|
+
@keyframes so-page-deeper { from { opacity: 0; transform: translateX(8px); } }
|
|
2687
|
+
@keyframes so-page-shallower { from { opacity: 0; transform: translateX(-8px); } }
|
|
2644
2688
|
/* UI polish 2026-09-13 — the motion contract. Feedback transitions run
|
|
2645
2689
|
140–200 ms on color, border, shadow, opacity, and transform only; an
|
|
2646
2690
|
overlay's entrance is at most 220 ms of opacity + transform. Nothing
|
|
@@ -2670,7 +2714,7 @@ button { min-height: 44px; }
|
|
|
2670
2714
|
@keyframes fade { from { opacity: 0; } }
|
|
2671
2715
|
@media (prefers-reduced-motion: reduce) {
|
|
2672
2716
|
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
2673
|
-
::view-transition-group(*), ::view-transition-old(
|
|
2717
|
+
::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
|
|
2674
2718
|
button:active, :is(.button-link, .side .new-task, .content .new-task, .result-feedback-link, .next-pager .skip):active { transform: none; }
|
|
2675
2719
|
@media (hover: hover) and (pointer: fine) { button:hover, .side nav a:hover, .chat-project-card:hover, .lane-card:hover, .decide-card:hover, .menu-row:hover { transform: none; } }
|
|
2676
2720
|
}
|
|
@@ -2854,9 +2898,9 @@ html.so-swap::view-transition-old(*):only-child { animation: so-card-out 160ms e
|
|
|
2854
2898
|
.tc-c0 { color: var(--so-ink); stroke: var(--so-ink); }
|
|
2855
2899
|
.tc-c1 { color: var(--so-info); stroke: var(--so-info); }
|
|
2856
2900
|
.tc-c2 { color: var(--so-success); stroke: var(--so-success); }
|
|
2857
|
-
.tc-c3 { color: var(--so-
|
|
2901
|
+
.tc-c3 { color: var(--so-muted); stroke: var(--so-muted); }
|
|
2858
2902
|
.tc-c4 { color: var(--so-danger); stroke: var(--so-danger); }
|
|
2859
|
-
.tc-c5 { color: var(--so-
|
|
2903
|
+
.tc-c5 { color: var(--so-input-line); stroke: var(--so-input-line); }
|
|
2860
2904
|
.tc-line { margin: 0; display: grid; gap: var(--so-space-xs); }
|
|
2861
2905
|
.tc-line svg { width: 100%; height: 90px; }
|
|
2862
2906
|
.tc-line figcaption { font-size: 11.5px; color: var(--so-muted); }
|
package/dist/server/home.d.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Home ("/",
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Home ("/", docs/plans/live-pages-and-home.md B1): what needs this person, grouped by what is asked (Approve a plan,
|
|
3
|
+
* Answer a question, Something went wrong, Review results, and Needs you for the rest), each group with its exact
|
|
4
|
+
* count; one quiet line for what runs and waits; with nothing waiting, what runs and what finished today. The figures
|
|
5
|
+
* are read from the same work index the Tasks list filters, so a count and the list it opens always agree. Reads
|
|
6
|
+
* only: every act keeps its own route and rules (an agent's question is answered through POST /d/<id>/answer, the
|
|
7
|
+
* same one-click act as its own page).
|
|
6
8
|
*/
|
|
7
|
-
import type { BrowserHomeView } from "../browser-workspace.js";
|
|
9
|
+
import type { BrowserHomeAsk, BrowserHomeView } from "../browser-workspace.js";
|
|
8
10
|
import type { Store } from "../store.js";
|
|
9
11
|
import type { WorkSummaryAccess } from "../work-summary.js";
|
|
10
|
-
/** How many waiting rows Home lists;
|
|
12
|
+
/** How many waiting rows Home lists; each group says how many more open in Tasks. */
|
|
11
13
|
export declare const HOME_NEEDS_SHOWN = 30;
|
|
14
|
+
/** How many rows finished today Home lists; its figure counts them all. */
|
|
15
|
+
export declare const HOME_DONE_SHOWN = 8;
|
|
16
|
+
/** Each group's heading, in the order Home shows them. */
|
|
17
|
+
export declare const HOME_ASK_LABEL: Readonly<Record<BrowserHomeAsk, string>>;
|
|
12
18
|
export type HomeFacts = {
|
|
13
19
|
store: Store;
|
|
14
20
|
now: Date;
|
|
@@ -16,7 +22,7 @@ export type HomeFacts = {
|
|
|
16
22
|
evidenceRoot?: string;
|
|
17
23
|
/** The project in view (already admitted), or null for every project. */
|
|
18
24
|
project: string | null;
|
|
19
|
-
/** Every admitted project, for the
|
|
25
|
+
/** Every admitted project, for the project filter. */
|
|
20
26
|
projects: readonly {
|
|
21
27
|
name: string;
|
|
22
28
|
path: string;
|
|
@@ -31,12 +37,19 @@ export type HomeFacts = {
|
|
|
31
37
|
why: string;
|
|
32
38
|
road: "scope" | "agents";
|
|
33
39
|
} | null;
|
|
40
|
+
/** The lead's last message in this person's main thread, already read through the chat's own access check
|
|
41
|
+
* (pages.ts leadLastMessage); null when there is none or this person may not read that thread. */
|
|
42
|
+
leadMessage?: {
|
|
43
|
+
text: string;
|
|
44
|
+
at: string;
|
|
45
|
+
} | null;
|
|
34
46
|
};
|
|
35
|
-
/** "
|
|
36
|
-
export declare function homeSentence(needs: number,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
/** "19 tasks need you": the heading, said once, from the same total as the rail's count. */
|
|
48
|
+
export declare function homeSentence(needs: number, any: boolean): string;
|
|
49
|
+
/** The lead's words as one plain line: no markdown marks, no line breaks, cut to about one line. */
|
|
50
|
+
export declare function leadLineOf(text: string): string;
|
|
51
|
+
/** Midnight today on this computer's clock: "finished today" starts here. */
|
|
52
|
+
export declare function startOfToday(now: Date): Date;
|
|
53
|
+
/** Home's view for one person and scope, from four reads of the work index: what waits on you (with exact per-ask
|
|
54
|
+
* totals), what runs, what waits its turn, and what was completed today (filtered before its limit). */
|
|
42
55
|
export declare function homeView(facts: HomeFacts): BrowserHomeView;
|