@augurworks/augur 0.15.1
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/CANVAS.md +858 -0
- package/INSTALL.md +603 -0
- package/LICENSE +27 -0
- package/README.md +150 -0
- package/TRADEMARKS.md +72 -0
- package/agents/README.md +65 -0
- package/agents/canon-extract.md +117 -0
- package/agents/canon.md +151 -0
- package/agents/canvas.md +88 -0
- package/agents/currency.md +74 -0
- package/agents/drafts.md +92 -0
- package/agents/identity.md +148 -0
- package/agents/prototype-contract.md +69 -0
- package/agents/publishing.md +324 -0
- package/agents/review-feedback.md +76 -0
- package/agents/space-json.md +132 -0
- package/agents/ui-skill.md +101 -0
- package/agents/working-marks.md +86 -0
- package/brand/augur-eye.svg +3 -0
- package/brand/augur-icon.svg +41 -0
- package/brand/augur-mark.svg +5 -0
- package/brand/lentianova-bulky.otf +0 -0
- package/brand/lentianova-median.otf +0 -0
- package/brand/lentianova.woff2 +0 -0
- package/build.js +8409 -0
- package/changelog.md +133 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/package.json +57 -0
- package/pitis/PETS.md +121 -0
- package/pitis/README.md +117 -0
- package/pitis/index.html +266 -0
- package/pitis/piti.build.js +81 -0
- package/pitis/piti.js +929 -0
- package/pitis/roast-agent.md +185 -0
- package/realtime/src/index.js +119 -0
- package/realtime/wrangler.example.toml +44 -0
- package/scripts/adopt.mjs +106 -0
- package/scripts/board-snapshot.mjs +197 -0
- package/scripts/build-seed-pack.mjs +33 -0
- package/scripts/bundle-rekey.mjs +112 -0
- package/scripts/bundle-tenancy-rehearsal.mjs +781 -0
- package/scripts/canon-extract.mjs +578 -0
- package/scripts/canon.mjs +599 -0
- package/scripts/canvas-screen.mjs +333 -0
- package/scripts/check-local.mjs +136 -0
- package/scripts/clawd-art.mjs +75 -0
- package/scripts/clawd-canvas.mjs +741 -0
- package/scripts/cli.mjs +100 -0
- package/scripts/clone.mjs +422 -0
- package/scripts/close.mjs +30 -0
- package/scripts/connect.mjs +103 -0
- package/scripts/deploy.mjs +157 -0
- package/scripts/dev.mjs +54 -0
- package/scripts/doc-lint.mjs +143 -0
- package/scripts/export.mjs +300 -0
- package/scripts/fork.mjs +64 -0
- package/scripts/freeze.mjs +74 -0
- package/scripts/frontdoor-parity.mjs +180 -0
- package/scripts/hook.mjs +67 -0
- package/scripts/identity-rekey.mjs +112 -0
- package/scripts/init.mjs +122 -0
- package/scripts/land.mjs +40 -0
- package/scripts/lib/adapters.mjs +156 -0
- package/scripts/lib/board-snapshot.mjs +511 -0
- package/scripts/lib/draft.mjs +385 -0
- package/scripts/lib/graduate.mjs +276 -0
- package/scripts/lib/instance.mjs +74 -0
- package/scripts/lib/marks.mjs +107 -0
- package/scripts/lib/materialize.mjs +138 -0
- package/scripts/lib/merge3.mjs +124 -0
- package/scripts/lib/offline-posture.mjs +120 -0
- package/scripts/lib/offline-respawn.mjs +18 -0
- package/scripts/lib/offline-wrangler.mjs +78 -0
- package/scripts/lib/publish-compose.mjs +12 -0
- package/scripts/lib/publish-conflict.mjs +78 -0
- package/scripts/lib/publish-evidence.mjs +144 -0
- package/scripts/lib/refine-compare.mjs +140 -0
- package/scripts/lib/refine-ledger.mjs +150 -0
- package/scripts/lib/refine-png.mjs +149 -0
- package/scripts/lib/seed-pack-build.mjs +141 -0
- package/scripts/lib/state-compare.mjs +68 -0
- package/scripts/lib/store.mjs +122 -0
- package/scripts/login.mjs +84 -0
- package/scripts/mark.mjs +112 -0
- package/scripts/migrate-board-keys.mjs +164 -0
- package/scripts/migrate.mjs +265 -0
- package/scripts/no-foreign-vocabulary.mjs +216 -0
- package/scripts/no-product-names.mjs +157 -0
- package/scripts/no-tenant-globals.mjs +1134 -0
- package/scripts/offline.mjs +247 -0
- package/scripts/og.mjs +170 -0
- package/scripts/one-tenant-resolver.mjs +146 -0
- package/scripts/open.mjs +63 -0
- package/scripts/publish.mjs +1041 -0
- package/scripts/read.mjs +31 -0
- package/scripts/refine.mjs +455 -0
- package/scripts/release-drift.mjs +138 -0
- package/scripts/restore.mjs +322 -0
- package/scripts/review.mjs +125 -0
- package/scripts/save.mjs +21 -0
- package/scripts/shell-lint.mjs +189 -0
- package/scripts/ship.mjs +460 -0
- package/scripts/shoot.mjs +264 -0
- package/scripts/smoke-invite.mjs +113 -0
- package/scripts/state-inventory.mjs +223 -0
- package/scripts/status.mjs +177 -0
- package/scripts/sync.mjs +27 -0
- package/scripts/tenant-do-rehearsal.mjs +1314 -0
- package/scripts/ui-copy-lint.mjs +202 -0
- package/scripts/watch.mjs +32 -0
- package/scripts/wrangler-preflight.mjs +246 -0
- package/seed/CANON.md +43 -0
- package/seed/README.md +119 -0
- package/seed/prototype-status.json +9 -0
- package/seed/registry.json +88 -0
- package/seed/skills/starter-ui/SKILL.md +52 -0
- package/seed/skills/starter-ui/skill.json +11 -0
- package/seed/skills/starter-ui/starter-tokens.css +73 -0
- package/seed/skills/starter-ui/starter-ui.css +318 -0
- package/seed/skills/starter-ui/starter-ui.js +50 -0
- package/seed/space.json +5 -0
- package/seed/start-here/prototypes/connect-your-terminal/index.html +428 -0
- package/seed/start-here/prototypes/connect-your-terminal/preview.webp +0 -0
- package/seed/start-here/prototypes/sample-with-comments/index.html +172 -0
- package/seed/start-here/prototypes/sample-with-comments/preview.webp +0 -0
- package/seed/start-here/prototypes/set-up-your-design-system/index.html +353 -0
- package/seed/start-here/prototypes/set-up-your-design-system/preview.webp +0 -0
- package/seed/threads.json +52 -0
- package/seed/worked-examples/prototypes/field-readings/index.html +273 -0
- package/seed/worked-examples/prototypes/field-readings/preview.webp +0 -0
- package/seed/worked-examples/prototypes/slide-deck/index.html +214 -0
- package/seed/worked-examples/prototypes/slide-deck/preview.webp +0 -0
- package/seed/worked-examples/prototypes/specimen-viewer/index.html +203 -0
- package/seed/worked-examples/prototypes/specimen-viewer/preview.webp +0 -0
- package/src/_worker.js +12668 -0
- package/src/board-key.mjs +57 -0
- package/src/board-room.mjs +881 -0
- package/src/bundle-keys.mjs +138 -0
- package/src/canon/collect-in-browser.js +215 -0
- package/src/canon/emit.mjs +232 -0
- package/src/canon/observe.mjs +260 -0
- package/src/canon/schema.mjs +451 -0
- package/src/canvas/DSEG-LICENSE.txt +95 -0
- package/src/canvas/DSEG7Classic-Bold.woff2 +0 -0
- package/src/canvas/canvas.css +885 -0
- package/src/canvas/canvas.js +5780 -0
- package/src/canvas/capture.js +485 -0
- package/src/chrome/appchrome.mjs +568 -0
- package/src/currency.mjs +203 -0
- package/src/delete-confirmation.mjs +254 -0
- package/src/drafts/drafts.js +326 -0
- package/src/entry.js +58 -0
- package/src/galleries.mjs +400 -0
- package/src/health-cron.mjs +173 -0
- package/src/identity.json +1 -0
- package/src/kv-codec.mjs +135 -0
- package/src/kv-identity.mjs +259 -0
- package/src/mail.mjs +599 -0
- package/src/provenance.mjs +74 -0
- package/src/publish-compose.mjs +363 -0
- package/src/publish-fork.mjs +221 -0
- package/src/publish-units.mjs +44 -0
- package/src/purge.mjs +81 -0
- package/src/review/COMMENTING-UX.md +306 -0
- package/src/review/cat.png +0 -0
- package/src/review/comment-cursor.svg +7 -0
- package/src/review/comments.js +1738 -0
- package/src/room-ticket.mjs +96 -0
- package/src/roster-chip.mjs +32 -0
- package/src/seed-pack.mjs +261 -0
- package/src/state-inventory.mjs +249 -0
- package/src/sw-logic.mjs +48 -0
- package/src/tenant-cache.mjs +141 -0
- package/src/tenant-context.mjs +386 -0
- package/src/tenant-do.js +2940 -0
- package/src/tenant-host.mjs +144 -0
- package/src/tenant-quotas.mjs +140 -0
- package/src/unit-core.mjs +103 -0
- package/src/unit-object.mjs +338 -0
- package/templates/README.md +67 -0
- package/templates/shell/deploy.yml +145 -0
- package/templates/shell/engine-bump.yml +142 -0
- package/templates/shell/health.yml +233 -0
- package/templates/shell/kv-backup.yml +201 -0
- package/templates/shell/roster-update.yml +110 -0
- package/templates/shell/space-preflight.yml +35 -0
- package/templates/shell/space-rebake.yml +79 -0
- package/templates/shell/store-backup.yml +152 -0
- package/templates/shell/wrangler.example.toml +266 -0
- package/templates/space/publish.yml +91 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
// Shared, PURE page-level chrome renderer — the rail, tab bar, mobile header and the
|
|
2
|
+
// body-level overlays (help drawer, settings modal, pinned/profile sheets). Imported by
|
|
3
|
+
// BOTH build.js (bakes it into every page) and src/_worker.js (re-renders it at serve
|
|
4
|
+
// time so an engine deploy updates every space instantly — runtime-chrome).
|
|
5
|
+
//
|
|
6
|
+
// It is deliberately self-contained: it reads NOTHING from a module global. Every value
|
|
7
|
+
// that varies per page arrives in the `state` argument:
|
|
8
|
+
// state = { spaces, activeSpace, opportunities, hasPlayground[, base, projectsLabel] }
|
|
9
|
+
// (`base`/`projectsLabel` are optional — derived from the active space entry otherwise).
|
|
10
|
+
// The co-located constants/icons/utilities below are the ONLY things it depends on, so
|
|
11
|
+
// the worker can import it with no build.js in scope.
|
|
12
|
+
|
|
13
|
+
// The SITE shell version — rail foot, chrome bundle filenames, the marker's data-ui.
|
|
14
|
+
// Single source of truth; build.js imports this rather than redeclaring it.
|
|
15
|
+
export const UI_VERSION = "1.16";
|
|
16
|
+
|
|
17
|
+
// ── Small pure utilities (shared with build.js, which imports them from here) ──
|
|
18
|
+
export const escAttr = (s) =>
|
|
19
|
+
String(s).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
20
|
+
|
|
21
|
+
// Words titleCase() shouts instead of capitalising, so a folder named `api-explorer`
|
|
22
|
+
// reads "API Explorer". The bar is deliberately high: an entry here changes how EVERY
|
|
23
|
+
// workspace's cards, breadcrumbs, search results and rail entries are spelled, so only
|
|
24
|
+
// acronyms that are general web vocabulary belong. A workspace's own slug prefixes do
|
|
25
|
+
// not — they read as ordinary words everywhere else, and the engine has no business
|
|
26
|
+
// knowing which two letters one team puts in front of half its folders.
|
|
27
|
+
// A frozen ARRAY, not a Set: Object.freeze leaves `.add()` working on a Set, so a Set is
|
|
28
|
+
// a table this engine cannot make un-writable. Eleven entries, tested with `.includes`.
|
|
29
|
+
const ACRONYMS = Object.freeze(["sms", "ui", "ux", "uxui", "api", "url", "faq", "sso", "cta", "pdf", "csv"]);
|
|
30
|
+
export function titleCase(slug) {
|
|
31
|
+
return slug
|
|
32
|
+
.replace(/[-_]/g, " ")
|
|
33
|
+
.replace(/\S+/g, (w) =>
|
|
34
|
+
ACRONYMS.includes(w.toLowerCase()) ? w.toUpperCase() : w.charAt(0).toUpperCase() + w.slice(1)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function fmtDate(ms) {
|
|
39
|
+
if (!ms) return "";
|
|
40
|
+
return new Date(ms).toLocaleDateString("en-US", {
|
|
41
|
+
year: "numeric",
|
|
42
|
+
month: "short",
|
|
43
|
+
day: "numeric",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* How long ago, as words: "3 days", "7 months", "" for anything under a minute.
|
|
49
|
+
*
|
|
50
|
+
* ONE COUNTER, so the two sentences a card can carry — "Edited 3 days ago" and the
|
|
51
|
+
* currency line's "Untouched for 7 months" — can never disagree about the number. A
|
|
52
|
+
* second unit table is how one card ends up saying two different ages of the same file.
|
|
53
|
+
* `now` is a parameter so a test can state the clock instead of arranging one.
|
|
54
|
+
*/
|
|
55
|
+
export function spanWords(ms, now = Date.now()) {
|
|
56
|
+
const sec = Math.round(Math.max(0, now - ms) / 1000);
|
|
57
|
+
const units = [
|
|
58
|
+
["year", 31536000],
|
|
59
|
+
["month", 2592000],
|
|
60
|
+
["day", 86400],
|
|
61
|
+
["hour", 3600],
|
|
62
|
+
["minute", 60],
|
|
63
|
+
];
|
|
64
|
+
for (const [name, s] of units) {
|
|
65
|
+
const v = Math.floor(sec / s);
|
|
66
|
+
if (v >= 1) return `${v} ${name}${v > 1 ? "s" : ""}`;
|
|
67
|
+
}
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function relTime(ms, now = Date.now()) {
|
|
72
|
+
if (!ms) return "";
|
|
73
|
+
const span = spanWords(ms, now);
|
|
74
|
+
return span ? `Edited ${span} ago` : "Edited just now";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Icons / marks used by the chrome (co-located so the module is self-contained) ──
|
|
78
|
+
export const ic = (inner) => `<svg class="gvic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${inner}</svg>`;
|
|
79
|
+
|
|
80
|
+
const GV_MARK = `<img class="gvmark" src="/augur-eye.svg" alt="" aria-hidden="true" width="24" height="24" />`;
|
|
81
|
+
const SEARCH_ICON = `<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2"/><path d="M21 21l-4.3-4.3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>`;
|
|
82
|
+
|
|
83
|
+
const IC_HOME = ic(`<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>`); // layout-grid
|
|
84
|
+
const IC_PLAY = ic(`<path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"/><path d="M6.453 15h11.094"/><path d="M8.5 2h7"/>`); // flask-conical
|
|
85
|
+
const IC_FOLDER = ic(`<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>`); // folder
|
|
86
|
+
const IC_PRIM = ic(`<path d="M8.3 10a.7.7 0 0 1-.626-1.079L11.4 3a.7.7 0 0 1 1.198-.043L16.3 8.9a.7.7 0 0 1-.572 1.1Z"/><rect x="3" y="14" width="7" height="7" rx="1"/><circle cx="17.5" cy="17.5" r="3.5"/>`); // shapes
|
|
87
|
+
const IC_COMP = ic(`<path d="M15.536 11.293a1 1 0 0 0 0 1.414l2.376 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z"/><path d="M2.297 11.293a1 1 0 0 0 0 1.414l2.377 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414L6.088 8.916a1 1 0 0 0-1.414 0z"/><path d="M8.916 17.912a1 1 0 0 0 0 1.415l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.415l-2.377-2.376a1 1 0 0 0-1.414 0z"/><path d="M8.916 4.674a1 1 0 0 0 0 1.414l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z"/>`); // component
|
|
88
|
+
const IC_PAGE = ic(`<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M10 4v4"/><path d="M2 8h20"/><path d="M6 4v4"/>`); // app-window (pages are websites, not paper)
|
|
89
|
+
const IC_RESEARCH = ic(`<path d="M6 3h8l4 4v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z"/><path d="M14 3v4h4"/><path d="M8.5 13h7M8.5 16.5h4.5"/>`); // document with text lines (internal research/context docs)
|
|
90
|
+
const IC_LIBRARY = ic(`<path d="m16 6 4 14"/><path d="M12 6v14"/><path d="M8 8v12"/><path d="M4 4v16"/>`); // library
|
|
91
|
+
const IC_CHANGELOG = ic(`<path d="M12 8v4l3 2"/><path d="M3.05 11a9 9 0 1 1 .5 4"/><path d="M3 21v-5h5"/>`); // history (clock + counter-rotate)
|
|
92
|
+
const IC_GEAR = ic(`<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>`); // settings
|
|
93
|
+
const IC_SIGNOUT = ic(`<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>`); // log-out
|
|
94
|
+
const IC_USERS = ic(`<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>`); // users — admin People
|
|
95
|
+
const IC_SEARCH = ic(`<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>`); // search — mobile tab bar (.gvic-sized; SEARCH_ICON is the un-classed header variant)
|
|
96
|
+
const IC_LOCK = ic(`<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>`); // lock — marks a "Change …" that isn't wired yet
|
|
97
|
+
const IC_SLIDERS = ic(`<line x1="4" x2="4" y1="21" y2="14"/><line x1="4" x2="4" y1="10" y2="3"/><line x1="12" x2="12" y1="21" y2="12"/><line x1="12" x2="12" y1="8" y2="3"/><line x1="20" x2="20" y1="21" y2="16"/><line x1="20" x2="20" y1="12" y2="3"/><line x1="2" x2="6" y1="14" y2="14"/><line x1="10" x2="14" y1="8" y2="8"/><line x1="18" x2="22" y1="16" y2="16"/>`); // sliders-vertical (account settings)
|
|
98
|
+
const IC_CLOSE = ic(`<path d="M18 6 6 18"/><path d="m6 6 12 12"/>`); // x
|
|
99
|
+
const IC_TOKEN = ic(`<circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/>`); // palette (tokens)
|
|
100
|
+
const IC_PATTERN = ic(`<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><path d="M10 6.5h4M6.5 10v4M17.5 10v4M10 17.5h4"/>`); // grid + links (patterns)
|
|
101
|
+
// Pushpin glyph for the tab bar's Pinned tab specifically — a star reads as "favourite"
|
|
102
|
+
// floating alone in a primary nav slot; the lucide `pin` says "pinned" directly.
|
|
103
|
+
const IC_PIN = ic(`<path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/>`);
|
|
104
|
+
|
|
105
|
+
// The library's own sections — used both to build its rail and to know when the rail
|
|
106
|
+
// should BE the library's (see renderAppChrome).
|
|
107
|
+
const LIB_KEYS = Object.freeze(["tokens", "base", "components", "patterns", "pages", "primitives"]);
|
|
108
|
+
|
|
109
|
+
// ── Per-page state derivation ────────────────────────────────────────────────
|
|
110
|
+
// The active space object out of the space list, and the two ambient values the rail
|
|
111
|
+
// used to read from build globals: the URL base (space scope) and the Projects label.
|
|
112
|
+
// Both prefer an explicit value on `state` (build passes it), else derive from the
|
|
113
|
+
// active space entry — so the worker can pass just { spaces, activeSpace } and still
|
|
114
|
+
// resolve the correct base for a non-default space.
|
|
115
|
+
function activeSpaceOf(state) {
|
|
116
|
+
const spaces = state.spaces || [];
|
|
117
|
+
return spaces.find((s) => s.id === state.activeSpace) || spaces[0] || null;
|
|
118
|
+
}
|
|
119
|
+
function baseOf(state) {
|
|
120
|
+
if (typeof state.base === "string") return state.base;
|
|
121
|
+
const a = activeSpaceOf(state);
|
|
122
|
+
if (!a) return "";
|
|
123
|
+
return typeof a.base === "string" ? a.base : (a.default ? "" : `/${a.id}`);
|
|
124
|
+
}
|
|
125
|
+
function projectsLabelOf(state) {
|
|
126
|
+
if (state.projectsLabel) return state.projectsLabel;
|
|
127
|
+
const a = activeSpaceOf(state);
|
|
128
|
+
return (a && a.projectsLabel) || "Projects";
|
|
129
|
+
}
|
|
130
|
+
// What the WORKSPACE has to say for itself in the Help drawer — space.json "help",
|
|
131
|
+
// already normalised by discoverSpaces() to [{title, items[]}]. Read off the active
|
|
132
|
+
// space entry exactly as projectsLabel is, so the serve worker renders it from the
|
|
133
|
+
// routing/manifest data it already has and needs no build global. Absent ⇒ [] ⇒ the
|
|
134
|
+
// drawer is the engine's own two sections and nothing else.
|
|
135
|
+
function spaceHelpOf(state) {
|
|
136
|
+
const a = activeSpaceOf(state);
|
|
137
|
+
return Array.isArray(a && a.help) ? a.help : [];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── Research/context surface — gated metadata only (count + filenames on click) ──
|
|
141
|
+
function researchLabel(n) { return `${n} research ${n === 1 ? "file" : "files"}`; }
|
|
142
|
+
function researchListItems(research) {
|
|
143
|
+
return research
|
|
144
|
+
.map((r) => `<li><span class="research-pop__name">${escAttr(r.name)}</span><span class="research-pop__date" title="${escAttr(fmtDate(r.mtimeMs))}">${relTime(r.mtimeMs)}</span></li>`)
|
|
145
|
+
.join("");
|
|
146
|
+
}
|
|
147
|
+
export function researchChip(research) {
|
|
148
|
+
if (!research || !research.length) return "";
|
|
149
|
+
const label = researchLabel(research.length);
|
|
150
|
+
return `<span class="research-wrap"><button type="button" class="research-chip" aria-expanded="false" aria-label="${label}" title="${label}">${IC_RESEARCH}<span class="research-chip__n">${research.length}</span></button><div class="research-pop" role="group" aria-label="Research & context files" hidden><div class="research-pop__head">Research & context</div><ul class="research-pop__list">${researchListItems(research)}</ul></div></span>`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// The omni search field — lives in the rail, filters whatever cards are on the right.
|
|
154
|
+
function railSearch(state) {
|
|
155
|
+
const S = (p) => baseOf(state) + p;
|
|
156
|
+
return `<div class="gvsearch" data-search-src="${S("/__search.json")}" data-search-base="${S("/")}">${SEARCH_ICON}` +
|
|
157
|
+
`<input type="text" data-filter placeholder="Search…" aria-label="Search content" autocomplete="off" autocapitalize="off" spellcheck="false" />` +
|
|
158
|
+
`<button type="button" class="gvsearch__clear" data-filter-clear aria-label="Clear search" hidden>×</button>` +
|
|
159
|
+
`<kbd data-filter-kbd>/</kbd></div>`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Profile chip — filled per-request by PROFILE_JS from /__me; hidden by default.
|
|
163
|
+
function profileChip() {
|
|
164
|
+
return `<div class="gvprof" data-prof hidden>
|
|
165
|
+
<button type="button" class="gvprof__btn" data-prof-toggle aria-haspopup="true" aria-expanded="false" aria-label="Account">
|
|
166
|
+
<span class="gvprof__av" data-prof-av aria-hidden="true"></span>
|
|
167
|
+
<span class="gvprof__name" data-prof-name>…</span>
|
|
168
|
+
<span class="gvprof__dot" data-prof-dot hidden title="Update available">!</span>
|
|
169
|
+
<svg class="gvprof__cv" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
|
|
170
|
+
</button>
|
|
171
|
+
<div class="gvprof__menu" data-prof-menu role="menu" hidden>
|
|
172
|
+
<div class="gvprof__id">
|
|
173
|
+
<span class="gvprof__av lg" data-prof-av aria-hidden="true"></span>
|
|
174
|
+
<span class="gvprof__idtext"><span class="gvprof__name" data-prof-name></span><span class="gvprof__email" data-prof-email></span></span>
|
|
175
|
+
</div>
|
|
176
|
+
<button type="button" class="gvprof__item" role="menuitem" data-prof-settings>${IC_SLIDERS}<span>Settings</span></button>
|
|
177
|
+
<a class="gvprof__item" href="/__logout" role="menuitem" data-prof-signout>${IC_SIGNOUT}<span>Sign out</span></a>
|
|
178
|
+
<div class="gvprof__ver" data-prof-ver hidden>
|
|
179
|
+
<span data-prof-vercur></span>
|
|
180
|
+
<a data-prof-verlink href="#" target="_blank" rel="noopener" hidden></a>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Space switcher — the active space's icon + name. Hidden when no space to name.
|
|
187
|
+
function spaceSwitcher(state) {
|
|
188
|
+
const spaces = state.spaces || [];
|
|
189
|
+
if (!spaces.length) return "";
|
|
190
|
+
const active = spaces.find((s) => s.id === state.activeSpace) || spaces[0];
|
|
191
|
+
// The admin-set workspace icon when one exists (the /__space-icon/<hash> stamped onto
|
|
192
|
+
// the space entry), else the baked seed. The client swap in build.js still corrects it
|
|
193
|
+
// from /__me, but rendering it here means the right icon on first paint, not a flash.
|
|
194
|
+
const iconSrc = active.icon || "/space-icon.png";
|
|
195
|
+
const icon = `<span class="gvspace__icon"><img src="${escAttr(iconSrc)}" alt="" width="20" height="20" data-space-icon /></span>`;
|
|
196
|
+
return `<div class="gvspace" data-space data-space-active="${escAttr(active.id)}">
|
|
197
|
+
<div class="gvspace__row">
|
|
198
|
+
<span class="gvspace__btn">
|
|
199
|
+
${icon}<span class="gvspace__name" data-space-name>${escAttr(active.name)}</span>
|
|
200
|
+
</span>
|
|
201
|
+
</div>
|
|
202
|
+
</div>`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// The persistent left rail: brand → search → Projects/Playground → Pinned → foot.
|
|
206
|
+
function sideRail(active, state) {
|
|
207
|
+
const S = (p) => baseOf(state) + p;
|
|
208
|
+
const item = (href, label, key, icon) =>
|
|
209
|
+
`<a href="${S(href)}"${active === key ? ' aria-current="page"' : ""}>${icon}<span>${label}</span></a>`;
|
|
210
|
+
const playground = state.hasPlayground ? item("/playground/", "Playground", "playground", IC_PLAY) : "";
|
|
211
|
+
const adminItem = `<a class="gvside__admin" href="/admin/" data-space-admin${
|
|
212
|
+
state.activeSpace ? ` data-space-id="${escAttr(state.activeSpace)}"` : ""
|
|
213
|
+
}>${IC_GEAR}<span>Admin</span></a>`;
|
|
214
|
+
const libraryItem = item("/tokens/", "Design system", "library", IC_LIBRARY);
|
|
215
|
+
const pinned = `<p class="gvside__label">Pinned</p>
|
|
216
|
+
<div class="gvside__group" data-pinned-list></div>
|
|
217
|
+
<p class="gvside__pinhint" data-pinned-empty hidden>Star a prototype to pin it here.</p>`;
|
|
218
|
+
return `<aside class="gvside" id="gvside" aria-label="Augur">
|
|
219
|
+
${profileChip()}
|
|
220
|
+
${railSearch(state)}
|
|
221
|
+
<div class="gvside__rule"></div>
|
|
222
|
+
${spaceSwitcher(state)}
|
|
223
|
+
<div class="gvside__scroll">
|
|
224
|
+
<div class="gvside__group">
|
|
225
|
+
${item("/", projectsLabelOf(state), "prototypes", IC_HOME)}
|
|
226
|
+
${playground}
|
|
227
|
+
${libraryItem}
|
|
228
|
+
${adminItem}
|
|
229
|
+
</div>
|
|
230
|
+
${pinned}
|
|
231
|
+
</div>
|
|
232
|
+
<div class="gvside__foot">
|
|
233
|
+
<div class="gvside__rule"></div>
|
|
234
|
+
<div class="gvside__group" style="margin-top:6px">
|
|
235
|
+
<a href="/changelog/"${active === "changelog" ? ' aria-current="page"' : ""}>${IC_CHANGELOG}<span>Changelog</span><span class="gvside__ver">v${UI_VERSION}</span></a>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</aside>`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Mobile bottom tab bar. Mirrors the same three-way branch renderAppChrome() computes.
|
|
242
|
+
function tabBar(active, state) {
|
|
243
|
+
const S = (p) => baseOf(state) + p;
|
|
244
|
+
const tab = (href, label, key, icon, extraAttrs) =>
|
|
245
|
+
`<a class="gvtab" href="${S(href)}"${active === key ? ' aria-current="page"' : ""}${extraAttrs || ""}>${icon}<span>${label}</span></a>`;
|
|
246
|
+
const blankSlot = `<span class="gvtab is-blank" aria-hidden="true"></span>`;
|
|
247
|
+
|
|
248
|
+
if (active === "admin") {
|
|
249
|
+
return `<nav class="gvtabbar" aria-label="Workspace settings">
|
|
250
|
+
<button type="button" class="gvtab" data-admin-tab="people">${IC_USERS}<span>People</span></button>
|
|
251
|
+
<button type="button" class="gvtab" data-admin-tab="content">${IC_FOLDER}<span>Content</span></button>
|
|
252
|
+
<button type="button" class="gvtab" data-admin-tab="settings">${IC_GEAR}<span>Settings</span></button>
|
|
253
|
+
${blankSlot}${blankSlot}
|
|
254
|
+
</nav>`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (LIB_KEYS.includes(active)) {
|
|
258
|
+
return `<nav class="gvtabbar" aria-label="Design system">
|
|
259
|
+
${tab("/tokens/", "Tokens", "tokens", IC_TOKEN)}
|
|
260
|
+
${tab("/base/", "Base", "base", IC_PRIM)}
|
|
261
|
+
${tab("/components/", "Components", "components", IC_COMP)}
|
|
262
|
+
${tab("/patterns/", "Patterns", "patterns", IC_PATTERN)}
|
|
263
|
+
${tab("/pages/", "Pages", "pages", IC_PAGE)}
|
|
264
|
+
</nav>`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const playground = state.hasPlayground
|
|
268
|
+
? tab("/playground/", "Playground", "playground", IC_PLAY)
|
|
269
|
+
: blankSlot;
|
|
270
|
+
const projectsActive = active !== "playground" && active !== "changelog";
|
|
271
|
+
return `<nav class="gvtabbar" aria-label="Primary">
|
|
272
|
+
<a class="gvtab" href="${S("/")}"${projectsActive ? ' aria-current="page"' : ""}>${IC_HOME}<span>${projectsLabelOf(state)}</span></a>
|
|
273
|
+
${playground}
|
|
274
|
+
${tab("/tokens/", "Design system", "library", IC_LIBRARY)}
|
|
275
|
+
<button type="button" class="gvtab" data-tab-pinned aria-haspopup="dialog">${IC_PIN}<span>Pinned</span></button>
|
|
276
|
+
<button type="button" class="gvtab" data-mobile-search-toggle aria-label="Search">${IC_SEARCH}<span>Search</span></button>
|
|
277
|
+
<button type="button" class="gvtab" data-prof data-tab-profile aria-haspopup="dialog" hidden><span class="gvprof__av" data-prof-av aria-hidden="true"></span><span>Profile</span></button>
|
|
278
|
+
</nav>`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Second copy of the pinned list for mobile (the rail's is off-canvas).
|
|
282
|
+
function mobilePinnedSheet() {
|
|
283
|
+
return `<div class="gvsheet" id="gvpinsheet" data-pin-sheet hidden>
|
|
284
|
+
<div class="gvsheet__scrim" data-pin-sheet-scrim></div>
|
|
285
|
+
<div class="gvsheet__panel" role="dialog" aria-modal="true" aria-label="Pinned">
|
|
286
|
+
<div class="gvsheet__head">
|
|
287
|
+
<h2 class="gvsheet__title">Pinned</h2>
|
|
288
|
+
<button type="button" class="gvsheet__x" data-pin-sheet-close aria-label="Close">${IC_CLOSE}</button>
|
|
289
|
+
</div>
|
|
290
|
+
<div class="gvside__group" data-pinned-list></div>
|
|
291
|
+
<p class="gvside__pinhint" data-pinned-empty hidden>Star a prototype to pin it here.</p>
|
|
292
|
+
</div>
|
|
293
|
+
</div>`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Second copy of the profile chip's identity block/menu for mobile, plus Admin + Changelog.
|
|
297
|
+
function mobileProfileSheet(state) {
|
|
298
|
+
return `<div class="gvsheet" id="gvprofsheet" data-prof-sheet hidden>
|
|
299
|
+
<div class="gvsheet__scrim" data-prof-sheet-scrim></div>
|
|
300
|
+
<div class="gvsheet__panel" role="dialog" aria-modal="true" aria-label="Profile">
|
|
301
|
+
<div class="gvsheet__head">
|
|
302
|
+
<h2 class="gvsheet__title">Profile</h2>
|
|
303
|
+
<button type="button" class="gvsheet__x" data-prof-sheet-close aria-label="Close">${IC_CLOSE}</button>
|
|
304
|
+
</div>
|
|
305
|
+
${profileChip()}
|
|
306
|
+
<a class="gvprof__item gvside__admin" href="/admin/" data-space-admin${
|
|
307
|
+
state.activeSpace ? ` data-space-id="${escAttr(state.activeSpace)}"` : ""
|
|
308
|
+
}>${IC_GEAR}<span>Admin</span></a>
|
|
309
|
+
<a class="gvprof__item" href="/changelog/">${IC_CHANGELOG}<span>Changelog</span></a>
|
|
310
|
+
</div>
|
|
311
|
+
</div>`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// The workspace's own Help sections, appended AFTER the engine's. Plain text, escaped:
|
|
315
|
+
// space.json is a config file, not a place to author markup, and this string is baked
|
|
316
|
+
// into every page of the site. A section with no title or no lines renders nothing.
|
|
317
|
+
// The shape checks repeat parseSpaceHelp's on purpose — the worker reads these entries
|
|
318
|
+
// off a manifest some other build wrote, so the renderer never assumes a well-formed one.
|
|
319
|
+
function spaceHelpSections(state) {
|
|
320
|
+
return spaceHelpOf(state).map((s) => {
|
|
321
|
+
const title = escAttr(String((s && s.title) || "").trim());
|
|
322
|
+
const items = (Array.isArray(s && s.items) ? s.items : [])
|
|
323
|
+
.filter((i) => typeof i === "string" && i.trim())
|
|
324
|
+
.map((i) => `<li>${escAttr(i.trim())}</li>`)
|
|
325
|
+
.join("");
|
|
326
|
+
return title && items ? `\n\n <h4>${title}</h4>\n <ul>${items}</ul>` : "";
|
|
327
|
+
}).join("");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// The Help drawer — a right-side slide-in panel opened from the rail footer.
|
|
331
|
+
function helpDrawer(state) {
|
|
332
|
+
return `<div class="gvhelp" data-help hidden>
|
|
333
|
+
<div class="gvhelp__scrim" data-help-scrim></div>
|
|
334
|
+
<div class="gvhelp__panel" role="dialog" aria-modal="true" aria-label="Help">
|
|
335
|
+
<header class="gvhelp__head">
|
|
336
|
+
<h3 class="gvhelp__title">Help</h3>
|
|
337
|
+
<button type="button" class="gvhelp__x" data-help-close aria-label="Close help">${IC_CLOSE}</button>
|
|
338
|
+
</header>
|
|
339
|
+
<div class="gvhelp__tabs" role="tablist" aria-label="Help topics">
|
|
340
|
+
<button type="button" class="gvhelp__tab" data-help-tab="review" role="tab">Reviewing</button>
|
|
341
|
+
<button type="button" class="gvhelp__tab" data-help-tab="build" role="tab">Building</button>
|
|
342
|
+
</div>
|
|
343
|
+
<div class="gvhelp__body">
|
|
344
|
+
<section class="gvhelp__track" data-help-track="review" role="tabpanel">
|
|
345
|
+
<h4>Comment</h4>
|
|
346
|
+
<ul>
|
|
347
|
+
<li><kbd>Shift</kbd>+<kbd>C</kbd>: toggle review mode.</li>
|
|
348
|
+
<li>Click any element to drop a pin, type, press <kbd>Enter</kbd>.</li>
|
|
349
|
+
<li>Pins scope to the screen they were made on. Off-screen pins hide.</li>
|
|
350
|
+
<li><kbd>Esc</kbd>: exit review mode.</li>
|
|
351
|
+
</ul>
|
|
352
|
+
|
|
353
|
+
<h4>Layers view</h4>
|
|
354
|
+
<ul>
|
|
355
|
+
<li>In review mode, press <kbd>↑</kbd>/<kbd>↓</kbd> (or click the Layers pill, bottom-left).</li>
|
|
356
|
+
<li>Boxes every element by layer: Components, +Base, +Tokens.</li>
|
|
357
|
+
<li>+Tokens paints live spacing on each box.</li>
|
|
358
|
+
</ul>
|
|
359
|
+
|
|
360
|
+
<h4>Marks</h4>
|
|
361
|
+
<ul>
|
|
362
|
+
<li><b>Comments</b>: your pins and threads (review mode on).</li>
|
|
363
|
+
<li><b>Annotations</b>: notes pinned to stay visible with review off. Skipped by "resolve comments".</li>
|
|
364
|
+
<li><b>Status</b>: the badge on each card. Click to cycle Dev ready, In progress, Ignore.</li>
|
|
365
|
+
</ul>
|
|
366
|
+
|
|
367
|
+
<h4>Cards</h4>
|
|
368
|
+
<ul>
|
|
369
|
+
<li>Right-click a card: Open, Copy link, Download HTML, Rename, Edit description.</li>
|
|
370
|
+
<li>Star a card to pin it to the sidebar. Drag pinned items to reorder.</li>
|
|
371
|
+
</ul>
|
|
372
|
+
|
|
373
|
+
<h4>Search</h4>
|
|
374
|
+
<ul>
|
|
375
|
+
<li><kbd>/</kbd> focus. <kbd>Cmd</kbd>/<kbd>Ctrl</kbd>+<kbd>K</kbd> focus and select. <kbd>Esc</kbd> clear.</li>
|
|
376
|
+
</ul>
|
|
377
|
+
|
|
378
|
+
<h4>Replies appear</h4>
|
|
379
|
+
<ul><li>An agent may answer or resolve your comment in-thread. That is the comment loop (see Building).</li></ul>
|
|
380
|
+
</section>
|
|
381
|
+
|
|
382
|
+
<section class="gvhelp__track" data-help-track="build" role="tabpanel" hidden>
|
|
383
|
+
<h4>Prototypes</h4>
|
|
384
|
+
<ul>
|
|
385
|
+
<li>Self-contained static HTML, no build step — a prototype has to work opened straight from disk.</li>
|
|
386
|
+
<li>The design system is read-only from a prototype. Edit the skill's own source rather than copying its classes out.</li>
|
|
387
|
+
</ul>
|
|
388
|
+
|
|
389
|
+
<h4>Comment loop <span class="gvhelp__tag">maintainer</span></h4>
|
|
390
|
+
<ul>
|
|
391
|
+
<li><code>npm run review --open</code> lists open threads.</li>
|
|
392
|
+
<li>The agent fixes, replies, resolves in-thread. Put it on <code>/loop</code> to keep watching.</li>
|
|
393
|
+
<li>Not automated. You steer it.</li>
|
|
394
|
+
</ul>${spaceHelpSections(state)}
|
|
395
|
+
</section>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
</div>`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Account settings — a centred modal opened from the profile menu.
|
|
402
|
+
function settingsModal() {
|
|
403
|
+
return `<div class="gvset" data-set hidden>
|
|
404
|
+
<div class="gvset__scrim" data-set-scrim></div>
|
|
405
|
+
<div class="gvset__panel" role="dialog" aria-modal="true" aria-label="Settings">
|
|
406
|
+
<div class="gvset__head">
|
|
407
|
+
<div class="gvset__tabs" role="tablist" aria-label="Settings sections">
|
|
408
|
+
<button type="button" class="gvset__tab is-active" data-set-tab="account" role="tab" aria-selected="true">Account</button>
|
|
409
|
+
</div>
|
|
410
|
+
<button type="button" class="gvset__x" data-set-close aria-label="Close settings">${IC_CLOSE}</button>
|
|
411
|
+
</div>
|
|
412
|
+
<div class="gvset__body">
|
|
413
|
+
<section class="gvset__cols" data-set-panel="account" role="tabpanel" aria-label="Account">
|
|
414
|
+
<div class="gvset__avcol">
|
|
415
|
+
<span class="gvset__av" data-prof-av aria-hidden="true"></span>
|
|
416
|
+
<button type="button" class="gvset__edit" data-set-edit>Edit</button>
|
|
417
|
+
<p class="gvset__err" data-set-err hidden></p>
|
|
418
|
+
<input type="file" accept="image/*" data-set-file hidden />
|
|
419
|
+
</div>
|
|
420
|
+
<div class="gvset__fields">
|
|
421
|
+
<div>
|
|
422
|
+
<h3 class="gvset__label">Name</h3>
|
|
423
|
+
<div data-set-name-view>
|
|
424
|
+
<p class="gvset__value" data-prof-name></p>
|
|
425
|
+
<button type="button" class="gvset__link" data-set-rename>Change name</button>
|
|
426
|
+
</div>
|
|
427
|
+
<form class="gvset__form" data-set-name-form hidden>
|
|
428
|
+
<input type="text" class="gvset__input" data-set-name-input maxlength="60"
|
|
429
|
+
autocomplete="name" aria-label="Your name" />
|
|
430
|
+
<button type="submit" class="gvset__btn" data-set-name-save>Save</button>
|
|
431
|
+
<button type="button" class="gvset__btn gvset__btn--ghost" data-set-name-cancel>Cancel</button>
|
|
432
|
+
<span class="gvset__msg" data-set-name-msg aria-live="polite"></span>
|
|
433
|
+
</form>
|
|
434
|
+
</div>
|
|
435
|
+
<div>
|
|
436
|
+
<h3 class="gvset__label">Email</h3>
|
|
437
|
+
<p class="gvset__value" data-prof-email></p>
|
|
438
|
+
<button type="button" class="gvset__link" disabled title="Changing your email isn't available yet">${IC_LOCK}Change email</button>
|
|
439
|
+
</div>
|
|
440
|
+
<div>
|
|
441
|
+
<h3 class="gvset__label">Password</h3>
|
|
442
|
+
<p class="gvset__value">••••••••</p>
|
|
443
|
+
<button type="button" class="gvset__link" disabled title="Changing your own password isn't built yet — ask an admin of every workspace you belong to, or the instance admin, to reset it">${IC_LOCK}Change password</button>
|
|
444
|
+
</div>
|
|
445
|
+
<div><h3 class="gvset__label">Role</h3><p class="gvset__value" data-set-role></p></div>
|
|
446
|
+
</div>
|
|
447
|
+
</section>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
<div class="gvcrop" data-crop hidden>
|
|
452
|
+
<div class="gvcrop__scrim" data-crop-scrim></div>
|
|
453
|
+
<div class="gvcrop__panel" role="dialog" aria-modal="true" aria-label="Crop photo">
|
|
454
|
+
<button type="button" class="gvcrop__x" data-crop-close aria-label="Cancel">${IC_CLOSE}</button>
|
|
455
|
+
<div class="gvcrop__stage"><canvas class="gvcrop__canvas" data-crop-canvas width="576" height="576"></canvas></div>
|
|
456
|
+
<div class="gvcrop__zoom">
|
|
457
|
+
<button type="button" class="gvcrop__step" data-crop-out aria-label="Zoom out">−</button>
|
|
458
|
+
<input type="range" class="gvcrop__range" data-crop-range min="1" max="3" step="0.01" value="1" aria-label="Zoom" />
|
|
459
|
+
<button type="button" class="gvcrop__step" data-crop-in aria-label="Zoom in">+</button>
|
|
460
|
+
</div>
|
|
461
|
+
<div class="gvcrop__foot">
|
|
462
|
+
<button type="button" class="gvcrop__save" data-crop-save>Save image</button>
|
|
463
|
+
<p class="gvcrop__msg" data-crop-msg hidden></p>
|
|
464
|
+
</div>
|
|
465
|
+
</div>
|
|
466
|
+
</div>`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// The rail while inside the design system: profile chip, a way back, this section's tabs.
|
|
470
|
+
function libraryRail(active, state) {
|
|
471
|
+
const S = (p) => baseOf(state) + p;
|
|
472
|
+
const item = (href, label, key, icon) =>
|
|
473
|
+
`<a href="${S(href)}"${active === key ? ' aria-current="page"' : ""}>${icon}<span>${label}</span></a>`;
|
|
474
|
+
return `<aside class="gvside" id="gvside" aria-label="Design system">
|
|
475
|
+
${profileChip()}
|
|
476
|
+
<div class="gvside__rule"></div>
|
|
477
|
+
<a class="gvadmin__back" href="${S("/")}">
|
|
478
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
479
|
+
<span>Design system</span>
|
|
480
|
+
</a>
|
|
481
|
+
<div class="gvside__scroll">
|
|
482
|
+
<div class="gvside__group">
|
|
483
|
+
${item("/tokens/", "Tokens", "tokens", IC_TOKEN)}
|
|
484
|
+
${item("/base/", "Base", "base", IC_PRIM)}
|
|
485
|
+
${item("/components/", "Components", "components", IC_COMP)}
|
|
486
|
+
${item("/patterns/", "Patterns", "patterns", IC_PATTERN)}
|
|
487
|
+
${item("/pages/", "Pages", "pages", IC_PAGE)}
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
</aside>`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// The rail while inside workspace settings: profile chip, a back link, the sections.
|
|
494
|
+
function adminRail() {
|
|
495
|
+
const tab = (key, label) =>
|
|
496
|
+
`<button type="button" class="gvside__act gvadmin__tab" data-admin-tab="${key}">${label}</button>`;
|
|
497
|
+
return `<aside class="gvside" id="gvside" aria-label="Workspace settings">
|
|
498
|
+
${profileChip()}
|
|
499
|
+
<div class="gvside__rule"></div>
|
|
500
|
+
<a class="gvadmin__back" href="/" data-admin-back>
|
|
501
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
502
|
+
<span data-admin-space-name>Workspace</span>
|
|
503
|
+
</a>
|
|
504
|
+
<div class="gvside__scroll">
|
|
505
|
+
<div class="gvside__group" data-admin-nav>
|
|
506
|
+
${tab("people", "People")}
|
|
507
|
+
${tab("content", "Content")}
|
|
508
|
+
${tab("settings", "Settings")}
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
</aside>`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Full chrome injected at the top of <body>: slim mobile top bar + the rail + the
|
|
515
|
+
// drawer scrim (the last two are off-canvas / hidden on desktop via CSS).
|
|
516
|
+
export function renderAppChrome(active, state, opts = {}) {
|
|
517
|
+
const spaces = state.spaces || [];
|
|
518
|
+
const activeSpaceObj = spaces.find((s) => s.id === state.activeSpace) || spaces[0];
|
|
519
|
+
const isLibOrAdmin = active === "admin" || LIB_KEYS.includes(active);
|
|
520
|
+
const isOpportunity = !isLibOrAdmin && !!active && active !== "prototypes" && active !== "playground" && active !== "changelog";
|
|
521
|
+
const spaceName = activeSpaceObj ? activeSpaceObj.name : "augur";
|
|
522
|
+
const sectionLabel = active === "admin" ? "Workspace settings"
|
|
523
|
+
: LIB_KEYS.includes(active) ? "Design system"
|
|
524
|
+
: active === "changelog" ? "Changelog"
|
|
525
|
+
: active === "playground" ? "Playground"
|
|
526
|
+
: isOpportunity ? titleCase(active)
|
|
527
|
+
: projectsLabelOf(state);
|
|
528
|
+
// The mobile top bar's home mark. Unlike the rail switcher this has no client swap, so
|
|
529
|
+
// it must render the workspace's own icon (the admin-set /__space-icon override when set,
|
|
530
|
+
// else the baked seed) server-side — otherwise a workspace that shipped no space-icon.png
|
|
531
|
+
// wears the engine mark here even signed in.
|
|
532
|
+
const homeMark = activeSpaceObj
|
|
533
|
+
? `<img class="gvtop__logo" src="${escAttr(activeSpaceObj.icon || "/space-icon.png")}" alt="" width="22" height="22" />`
|
|
534
|
+
: `<span class="gvtop__logo gvtop__logo--mark">${GV_MARK}</span>`;
|
|
535
|
+
const S = (p) => baseOf(state) + p;
|
|
536
|
+
const crumbs = `<nav class="gvtop__crumbs" aria-label="Breadcrumb">
|
|
537
|
+
<a class="gvtop__crumb gvtop__crumb--home" href="${S("/")}">${homeMark}<span>${escAttr(spaceName)}</span></a>
|
|
538
|
+
<span class="gvtop__crumbsep" aria-hidden="true">›</span>
|
|
539
|
+
<span class="gvtop__crumb gvtop__crumb--current" aria-current="page">${escAttr(sectionLabel)}</span>
|
|
540
|
+
</nav>`;
|
|
541
|
+
const headerAction = opts.research && opts.research.length
|
|
542
|
+
? `<div class="gvtop__action">${researchChip(opts.research)}</div>`
|
|
543
|
+
: "";
|
|
544
|
+
const top = `<header class="gvtop">
|
|
545
|
+
<div class="gvtop__center">${crumbs}</div>
|
|
546
|
+
<div class="gvtop__searchwrap">${railSearch(state)}</div>
|
|
547
|
+
${headerAction}
|
|
548
|
+
</header>`;
|
|
549
|
+
const rail = active === "admin" ? adminRail()
|
|
550
|
+
: LIB_KEYS.includes(active) ? libraryRail(active, state)
|
|
551
|
+
: sideRail(active, state);
|
|
552
|
+
return `${top}${rail}<div class="gvscrim" data-side-scrim></div>${tabBar(active, state)}${mobilePinnedSheet()}${mobileProfileSheet(state)}${helpDrawer(state)}${settingsModal()}`;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Per-page data script: this space's base + the other spaces' bases, for the pins filter.
|
|
556
|
+
export function renderSpaceContextScript(state) {
|
|
557
|
+
const spaces = state.spaces || [];
|
|
558
|
+
const active = spaces.find((s) => s.id === state.activeSpace) || spaces[0] || { base: "" };
|
|
559
|
+
const others = spaces.filter((s) => !s.default).map((s) => s.base);
|
|
560
|
+
return `window.__GV_SPACE=${JSON.stringify({ base: active.base || "", others })};`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Delimiters wrapping the baked chrome region. HTML comments — inert, changing no
|
|
564
|
+
// rendering — carrying the space id + active tab so the worker can re-render the
|
|
565
|
+
// CURRENT engine's chrome in place at serve time (composeChrome in src/_worker.js).
|
|
566
|
+
export const CHROME_MARK_START = (space, active, hasPlayground) =>
|
|
567
|
+
`<!--gv-chrome-start data-space="${escAttr(space)}" data-active="${escAttr(active)}" data-playground="${hasPlayground ? "1" : "0"}" data-ui="${UI_VERSION}"-->`;
|
|
568
|
+
export const CHROME_MARK_END = `<!--gv-chrome-end-->`;
|