@zerwiz/ymir 0.1.16 → 0.1.18
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/.agents/config/app-repos.yaml +38 -0
- package/.claude/settings.json +31 -0
- package/.codex/hooks.json +43 -0
- package/.cursor/hooks.json +33 -0
- package/.pi/extensions/README.md +39 -0
- package/.pi/extensions/gna-pi-watch.ts +15 -0
- package/.pi/extensions/lib/ro-assistant-layout.ts +96 -0
- package/.pi/extensions/lib/ro-operational-user-layout.ts +145 -0
- package/.pi/extensions/lib/ro-visibility.ts +99 -0
- package/.pi/extensions/lib/ro-working-ship.ts +243 -0
- package/.pi/extensions/lib/rodd-operational-input.ts +62 -0
- package/.pi/extensions/lib/skuld-branch-dispatch.ts +252 -0
- package/.pi/extensions/lib/skuld-branch-model-picker.ts +77 -0
- package/.pi/extensions/lib/vordr-sessionstart-supervisor.mjs +48 -0
- package/.pi/extensions/ro.ts +11 -0
- package/.pi/extensions/skuld-branch-supervision.ts +11 -0
- package/.pi/extensions/syn-turnend-guard.ts +11 -0
- package/.pi/mcp.json +16 -0
- package/.pi/mcp.json.example +11 -0
- package/.pi/npm/.gitignore +2 -0
- package/.pi/settings.json +8 -0
- package/.pi/shared/extensions/gna-pi-watch.ts +750 -0
- package/.pi/shared/extensions/herdr-agent-state.ts +257 -0
- package/.pi/shared/extensions/open-editor.ts +509 -0
- package/.pi/shared/extensions/ro.ts +504 -0
- package/.pi/shared/extensions/skuld-branch-supervision.ts +1535 -0
- package/.pi/shared/extensions/syn-turnend-guard.ts +683 -0
- package/.pi/shared/extensions/todo.ts +133 -0
- package/.pi/shared/extensions/ymir-subagents.ts +287 -0
- package/.pi/shared/extensions/ymir-well.ts +130 -0
- package/CHANGELOG.md +23 -0
- package/assets/Ymir.md +4654 -0
- package/assets/healer.html +717 -0
- package/assets/icon-family/family-sheet.png +0 -0
- package/assets/icon-family/hlidskjalf.svg +1 -0
- package/assets/icon-family/sessrumnir.svg +1 -0
- package/assets/icon-family/smidja.svg +1 -0
- package/assets/icon-family/ymir.svg +1 -0
- package/assets/mimirswell.html +880 -0
- package/assets/mock/README.md +16 -0
- package/assets/mock/feeds.ts +175 -0
- package/assets/mock/mock.ts +408 -0
- package/assets/reference/docs/agent-control.md +122 -0
- package/assets/reference/docs/apodex/Appdex_as_Orchestrator_Planner_Reviewer_Assessment.md +176 -0
- package/assets/reference/docs/apodex/Final Report(1).md +198 -0
- package/assets/reference/docs/apodex/Final Report(2).md +54 -0
- package/assets/reference/docs/apodex/Final Report(3).md +40 -0
- package/assets/reference/docs/apodex/Final Report(4).md +234 -0
- package/assets/reference/docs/apodex/Final Report(5).md +13 -0
- package/assets/reference/docs/apodex/Final Report.md +82 -0
- package/assets/reference/docs/architecture.md +392 -0
- package/assets/reference/docs/arm-pretool-check.md +247 -0
- package/assets/reference/docs/calm-mode-feasibility.md +541 -0
- package/assets/reference/docs/calm.md +59 -0
- package/assets/reference/docs/captain-hold-lifecycle.md +96 -0
- package/assets/reference/docs/cd-guard.md +165 -0
- package/assets/reference/docs/cmux-backend.md +133 -0
- package/assets/reference/docs/codex-app-backend.md +57 -0
- package/assets/reference/docs/configuration.md +924 -0
- package/assets/reference/docs/documentation-audiences.json +468 -0
- package/assets/reference/docs/documentation-audiences.md +28 -0
- package/assets/reference/docs/examples/crew-dispatch.json +26 -0
- package/assets/reference/docs/examples/process-event-extension/file-signal.mjs +96 -0
- package/assets/reference/docs/examples/process-event-extension/firstmate-extension.json +15 -0
- package/assets/reference/docs/examples/watched-tools.json +24 -0
- package/assets/reference/docs/examples/wedge-alarm +22 -0
- package/assets/reference/docs/extension-bindings.md +237 -0
- package/assets/reference/docs/fm-test-isolation-proof.json +37 -0
- package/assets/reference/docs/fm-test-isolation-proof.md +149 -0
- package/assets/reference/docs/fm-test-portable-shards.md +118 -0
- package/assets/reference/docs/gitlab-merge-watch.md +275 -0
- package/assets/reference/docs/herdr-backend.md +346 -0
- package/assets/reference/docs/orca-backend.md +84 -0
- package/assets/reference/docs/pi-supervision-branch-poster.svg +125 -0
- package/assets/reference/docs/pi-supervision-branch.md +96 -0
- package/assets/reference/docs/plans/agentic-engineering-workflow.md +395 -0
- package/assets/reference/docs/plans/agentic-factory-implementation-plan.md +615 -0
- package/assets/reference/docs/plans/command-factory-enhancement-plan.md +420 -0
- package/assets/reference/docs/plans/phase-1-layer3-program-design.md +134 -0
- package/assets/reference/docs/plans/phase-2-vertical-slices.md +100 -0
- package/assets/reference/docs/plans/phase-3-measurable-goals.md +106 -0
- package/assets/reference/docs/plans/phase-4-context-budget.md +106 -0
- package/assets/reference/docs/plans/phase-5-pre-mortem-confidence.md +100 -0
- package/assets/reference/docs/plans/phase-6-incident-to-agent.md +109 -0
- package/assets/reference/docs/remote-secondmates.md +268 -0
- package/assets/reference/docs/scripts.md +146 -0
- package/assets/reference/docs/sessionstart-nudge.md +115 -0
- package/assets/reference/docs/subagent-guard.md +383 -0
- package/assets/reference/docs/supervision-protocols/claude.md +27 -0
- package/assets/reference/docs/supervision-protocols/codex.md +16 -0
- package/assets/reference/docs/supervision-protocols/cursor.md +31 -0
- package/assets/reference/docs/supervision-protocols/grok.md +39 -0
- package/assets/reference/docs/supervision-protocols/opencode.md +17 -0
- package/assets/reference/docs/supervision-protocols/pi.md +33 -0
- package/assets/reference/docs/supervision-protocols/unknown.md +13 -0
- package/assets/reference/docs/tmux-backend.md +115 -0
- package/assets/reference/docs/trace-context.md +117 -0
- package/assets/reference/docs/turnend-guard.md +170 -0
- package/assets/reference/docs/verification/dispatch-auth.md +204 -0
- package/assets/reference/docs/verification/muse.md +221 -0
- package/assets/reference/docs/verification/process-event-sources.md +208 -0
- package/assets/reference/docs/verification/public-followup.md +119 -0
- package/assets/reference/docs/verification/runtime-backends.md +1022 -0
- package/assets/reference/docs/verification/stow-memory.md +53 -0
- package/assets/reference/docs/verification/supervision.md +534 -0
- package/assets/reference/docs/verification/trace-context.md +31 -0
- package/assets/reference/docs/voice-relay.md +295 -0
- package/assets/reference/docs/watcher-continuity.md +119 -0
- package/assets/reference/docs/wedge-alarm.md +39 -0
- package/assets/reference/docs/zellij-backend.md +114 -0
- package/assets/reference/index.html +1172 -0
- package/assets/reference/skills/afk/SKILL.md +241 -0
- package/assets/reference/skills/ahoy/SKILL.md +56 -0
- package/assets/reference/skills/ask-user-authority/SKILL.md +56 -0
- package/assets/reference/skills/bearings/SKILL.md +161 -0
- package/assets/reference/skills/bearings/assets/board-template.html +731 -0
- package/assets/reference/skills/bootstrap-diagnostics/SKILL.md +68 -0
- package/assets/reference/skills/captain-hold-lifecycle/SKILL.md +54 -0
- package/assets/reference/skills/decision-hold-lifecycle/SKILL.md +15 -0
- package/assets/reference/skills/diagnostic-reasoning/SKILL.md +53 -0
- package/assets/reference/skills/firstmate-codexapp/SKILL.md +110 -0
- package/assets/reference/skills/firstmate-codexapp/agents/openai.yaml +4 -0
- package/assets/reference/skills/firstmate-coding-guidelines/SKILL.md +127 -0
- package/assets/reference/skills/firstmate-orca/SKILL.md +92 -0
- package/assets/reference/skills/fmx-respond/SKILL.md +283 -0
- package/assets/reference/skills/harness-adapters/SKILL.md +95 -0
- package/assets/reference/skills/harness-adapters/references/common/control-and-recovery.md +37 -0
- package/assets/reference/skills/harness-adapters/references/common/dispatch.md +32 -0
- package/assets/reference/skills/harness-adapters/references/common/model-and-effort.md +42 -0
- package/assets/reference/skills/harness-adapters/references/common/primary-hooks.md +40 -0
- package/assets/reference/skills/harness-adapters/references/harness/claude.md +55 -0
- package/assets/reference/skills/harness-adapters/references/harness/codex.md +43 -0
- package/assets/reference/skills/harness-adapters/references/harness/cursor.md +75 -0
- package/assets/reference/skills/harness-adapters/references/harness/grok.md +69 -0
- package/assets/reference/skills/harness-adapters/references/harness/kimi.md +51 -0
- package/assets/reference/skills/harness-adapters/references/harness/muse.md +70 -0
- package/assets/reference/skills/harness-adapters/references/harness/opencode.md +42 -0
- package/assets/reference/skills/harness-adapters/references/harness/pi.md +56 -0
- package/assets/reference/skills/process-event-sources/SKILL.md +137 -0
- package/assets/reference/skills/project-management/SKILL.md +92 -0
- package/assets/reference/skills/quota-array-dispatch/SKILL.md +131 -0
- package/assets/reference/skills/secondmate-provisioning/SKILL.md +254 -0
- package/assets/reference/skills/stow/SKILL.md +309 -0
- package/assets/reference/skills/stuck-crewmate-recovery/SKILL.md +54 -0
- package/assets/reference/skills/updatefirstmate/SKILL.md +64 -0
- package/assets/skills/README.md +5 -0
- package/assets/yggdrasil.html +649 -0
- package/assets/ymir-banner-01.png +0 -0
- package/assets/ymir-banner-02.png +0 -0
- package/assets/ymir-banner-03.png +0 -0
- package/assets/ymir-banner-04.png +0 -0
- package/assets/ymir-banner-05.png +0 -0
- package/assets/ymir-banner-06.png +0 -0
- package/assets/ymir-emblem-darksteel.svg +63 -0
- package/assets/ymir-emblem-forged.svg +55 -0
- package/assets/ymir-emblem-slate.svg +55 -0
- package/assets/ymir-mark-algiz-anvil.svg +9 -0
- package/assets/ymir-stave.svg +53 -0
- package/assets/ymir.html +563 -0
- package/bin/ymir-install.sh +6 -1
- package/data/learnings.md +33 -0
- package/data/local-models.md +34 -0
- package/data/operator.md +15 -0
- package/data/projects.md +28 -0
- package/data/realm.md +1 -0
- package/deploy/Containerfile +27 -0
- package/deploy/README.md +104 -0
- package/deploy/compose/compose.yaml +45 -0
- package/deploy/env.example +30 -0
- package/deploy/quadlet/ymir.container +57 -0
- package/package.json +10 -8
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Bearings - fleet board</title>
|
|
7
|
+
<style>
|
|
8
|
+
/* ============================================================
|
|
9
|
+
Bearings board - Firstmate Design System, inlined for portability.
|
|
10
|
+
Tokens copied verbatim from myfirstmate priv/static/assets/app.css
|
|
11
|
+
(the compiled adoption of .agents/skills/firstmate-design).
|
|
12
|
+
============================================================ */
|
|
13
|
+
@import url("https://fonts.googleapis.com/css2?family=Chango&family=Jost:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap");
|
|
14
|
+
|
|
15
|
+
:root {
|
|
16
|
+
--rust-700: #8f2f17; --rust-600: #a93a1f; --rust-500: #c0452a;
|
|
17
|
+
--rust-400: #d35f3f; --rust-300: #e08365; --rust-100: #f4d8c9; --rust-050: #fbece3;
|
|
18
|
+
--navy-700: #1a2238; --navy-600: #222c49; --navy-500: #2a3656;
|
|
19
|
+
--navy-300: #6c7796; --navy-100: #d9deea;
|
|
20
|
+
--gold-600: #b5791c; --gold-500: #e0a52e; --gold-300: #f0d38c; --gold-100: #f8ecc9;
|
|
21
|
+
--ocean-600: #2f6688; --ocean-500: #3c7ea6; --ocean-200: #b6d4e2; --ocean-050: #e8f1f5;
|
|
22
|
+
--sea-700: #234e3a; --sea-500: #2f6b4f; --sea-200: #b9d4c5; --sea-050: #e9f2ec;
|
|
23
|
+
--paper-000: #fbf4e2; --paper-100: #f6ecd3; --paper-200: #f0e3c4; --paper-300: #e7d6ae;
|
|
24
|
+
--cream-line: #ddc89c;
|
|
25
|
+
--ink-900: #241c14; --ink-700: #3f3224; --ink-500: #6f5e46; --ink-300: #9c8a6c;
|
|
26
|
+
--white: #fffdf7;
|
|
27
|
+
--bg-page: var(--paper-100);
|
|
28
|
+
--surface-card: var(--white);
|
|
29
|
+
--surface-card-warm: var(--paper-000);
|
|
30
|
+
--text-strong: var(--ink-900); --text-body: var(--ink-700);
|
|
31
|
+
--text-muted: var(--ink-500); --text-faint: var(--ink-300);
|
|
32
|
+
--border-default: var(--cream-line); --border-soft: var(--paper-300);
|
|
33
|
+
--status-online: var(--sea-500); --status-online-soft: var(--sea-050);
|
|
34
|
+
--status-warn: var(--gold-600); --status-warn-soft: var(--gold-100);
|
|
35
|
+
--status-danger: var(--rust-600); --status-danger-soft: var(--rust-050);
|
|
36
|
+
--status-info: var(--ocean-500); --status-info-soft: var(--ocean-050);
|
|
37
|
+
--font-display: "Chango", "Cooper Black", Rockwell, Georgia, serif;
|
|
38
|
+
--font-sans: "Jost", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
39
|
+
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
40
|
+
--fs-h3: 1.4rem; --fs-h4: 1.15rem; --fs-base: 1rem; --fs-sm: 0.9375rem;
|
|
41
|
+
--fs-xs: 0.8125rem; --fs-2xs: 0.6875rem;
|
|
42
|
+
--ls-caps: 0.16em;
|
|
43
|
+
--radius-xs: 6px; --radius-sm: 9px; --radius-md: 12px; --radius-banner: 7px;
|
|
44
|
+
--radius-lg: 18px; --radius-pill: 999px;
|
|
45
|
+
--shadow-sm: 0 1px 2px rgba(36, 28, 20, 0.06), 0 4px 10px rgba(36, 28, 20, 0.06);
|
|
46
|
+
--shadow-hard: 4px 4px 0 var(--ink-900);
|
|
47
|
+
--shadow-hard-sm: 3px 3px 0 var(--ink-900);
|
|
48
|
+
--texture-paper: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.045'/%3E%3C/svg%3E");
|
|
49
|
+
--focus-ring: 0 0 0 3px var(--gold-300);
|
|
50
|
+
--container-app: 1180px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
* { box-sizing: border-box; }
|
|
54
|
+
html, body { margin: 0; padding: 0; }
|
|
55
|
+
body {
|
|
56
|
+
font-family: var(--font-sans);
|
|
57
|
+
color: var(--text-body);
|
|
58
|
+
background: var(--bg-page);
|
|
59
|
+
background-image: var(--texture-paper);
|
|
60
|
+
line-height: 1.55;
|
|
61
|
+
-webkit-font-smoothing: antialiased;
|
|
62
|
+
}
|
|
63
|
+
a { color: inherit; text-decoration: none; }
|
|
64
|
+
|
|
65
|
+
/* ---- fm-badge (enamel sign-pin), verbatim contract ---- */
|
|
66
|
+
.fm-badge {
|
|
67
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
68
|
+
font-size: var(--fs-2xs); font-weight: 800; line-height: 1;
|
|
69
|
+
padding: 5px 9px 4px; text-transform: uppercase; letter-spacing: 0.07em;
|
|
70
|
+
border-radius: var(--radius-xs); border: 1.5px solid var(--ink-900);
|
|
71
|
+
box-shadow: 2px 2px 0 var(--ink-900); white-space: nowrap;
|
|
72
|
+
}
|
|
73
|
+
.fm-badge--online { background: var(--sea-500); color: var(--paper-000); }
|
|
74
|
+
.fm-badge--warn { background: var(--gold-500); color: var(--navy-700); }
|
|
75
|
+
.fm-badge--danger { background: var(--rust-600); color: var(--paper-000); }
|
|
76
|
+
.fm-badge--info { background: var(--ocean-500); color: var(--paper-000); }
|
|
77
|
+
.fm-badge--neutral { background: var(--paper-000); color: var(--ink-900); }
|
|
78
|
+
.fm-badge--solid { background: var(--rust-500); color: var(--paper-000); }
|
|
79
|
+
|
|
80
|
+
/* ---- fm-btn ---- */
|
|
81
|
+
.fm-btn {
|
|
82
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
|
83
|
+
font-family: var(--font-sans); font-weight: 800; line-height: 1; white-space: nowrap;
|
|
84
|
+
border: 2px solid transparent; border-radius: var(--radius-banner); cursor: pointer;
|
|
85
|
+
text-decoration: none; transition: background 120ms, border-color 120ms, transform 120ms, box-shadow 120ms;
|
|
86
|
+
}
|
|
87
|
+
.fm-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
|
|
88
|
+
.fm-btn:active { transform: translateY(1px); }
|
|
89
|
+
.fm-btn--sm { font-size: var(--fs-xs); padding: 8px 16px; }
|
|
90
|
+
.fm-btn--primary { background: var(--rust-500); color: var(--white); border-color: var(--ink-900); box-shadow: var(--shadow-hard-sm); }
|
|
91
|
+
.fm-btn--primary:hover { background: var(--rust-600); }
|
|
92
|
+
.fm-btn--gold { background: var(--gold-500); color: var(--navy-700); border-color: var(--ink-900); box-shadow: var(--shadow-hard-sm); }
|
|
93
|
+
.fm-btn--gold:hover { background: var(--gold-600); color: var(--white); }
|
|
94
|
+
.fm-btn[disabled] { opacity: 0.5; cursor: not-allowed; }
|
|
95
|
+
|
|
96
|
+
/* ---- fm-card ---- */
|
|
97
|
+
.fm-card {
|
|
98
|
+
background: var(--surface-card); border: 1px solid var(--border-default);
|
|
99
|
+
border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: hidden;
|
|
100
|
+
}
|
|
101
|
+
.fm-card--poster { border: 2px solid var(--ink-900); box-shadow: var(--shadow-hard); border-radius: var(--radius-md); }
|
|
102
|
+
.fm-card--warm { background: var(--surface-card-warm); }
|
|
103
|
+
|
|
104
|
+
/* ---- fm-sign (eyebrow section label) ---- */
|
|
105
|
+
.fm-sign {
|
|
106
|
+
display: inline-flex; align-items: center; gap: 8px; font-weight: 800;
|
|
107
|
+
text-transform: uppercase; letter-spacing: var(--ls-caps); font-size: var(--fs-xs);
|
|
108
|
+
line-height: 1; white-space: nowrap;
|
|
109
|
+
}
|
|
110
|
+
.fm-sign--eyebrow { color: var(--rust-500); }
|
|
111
|
+
.fm-sign--muted { color: var(--ink-500); }
|
|
112
|
+
.fm-sign svg { width: 14px; height: 14px; }
|
|
113
|
+
.fm-ico { width: 1.15em; height: 1.15em; flex: none; vertical-align: -0.18em; }
|
|
114
|
+
|
|
115
|
+
/* ============================================================
|
|
116
|
+
Board chrome (bb-*) - built on the same tokens
|
|
117
|
+
============================================================ */
|
|
118
|
+
.bb-nav {
|
|
119
|
+
position: sticky; top: 0; z-index: 20;
|
|
120
|
+
background: color-mix(in srgb, var(--paper-100) 86%, transparent);
|
|
121
|
+
backdrop-filter: saturate(150%) blur(10px);
|
|
122
|
+
border-bottom: 1px solid var(--border-default);
|
|
123
|
+
}
|
|
124
|
+
.bb-nav__inner {
|
|
125
|
+
max-width: var(--container-app); margin: 0 auto; padding: 0 28px; height: 64px;
|
|
126
|
+
display: flex; align-items: center; justify-content: space-between; gap: 16px;
|
|
127
|
+
}
|
|
128
|
+
.bb-brand { display: inline-flex; align-items: center; gap: 12px; }
|
|
129
|
+
.bb-brand__disc {
|
|
130
|
+
display: grid; place-items: center; width: 34px; height: 34px; flex: none;
|
|
131
|
+
border-radius: 999px; background: var(--rust-500); color: var(--paper-000);
|
|
132
|
+
border: 2px solid var(--ink-900); box-shadow: var(--shadow-hard-sm);
|
|
133
|
+
}
|
|
134
|
+
.bb-brand__disc svg { width: 60%; height: 60%; }
|
|
135
|
+
.bb-brand__wm { font-family: var(--font-display); font-size: 23px; color: var(--text-strong); line-height: 1; }
|
|
136
|
+
.bb-meta-mono { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-faint); white-space: nowrap; }
|
|
137
|
+
|
|
138
|
+
.bb-main {
|
|
139
|
+
max-width: var(--container-app); margin: 0 auto;
|
|
140
|
+
padding: 26px 28px 72px; display: flex; flex-direction: column; gap: 22px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* stat strip */
|
|
144
|
+
.bb-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
|
|
145
|
+
.bb-stat {
|
|
146
|
+
display: flex; flex-direction: column; gap: 4px; padding: 14px 16px; min-width: 0;
|
|
147
|
+
background: var(--surface-card-warm); border: 1px solid var(--border-soft); border-radius: var(--radius-md);
|
|
148
|
+
}
|
|
149
|
+
.bb-stat--call { background: var(--rust-050); border: 2px solid var(--rust-500); }
|
|
150
|
+
.bb-stat__num { font-family: var(--font-mono); font-size: var(--fs-h3); font-weight: 600; line-height: 1; color: var(--text-strong); }
|
|
151
|
+
.bb-stat--call .bb-stat__num { color: var(--rust-600); }
|
|
152
|
+
.bb-stat__label { font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
|
|
153
|
+
|
|
154
|
+
.bb-section { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
|
|
155
|
+
.bb-section__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
|
|
156
|
+
.bb-cols { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 22px; }
|
|
157
|
+
|
|
158
|
+
/* ---- Captain's Call: one card at a time, dealt off a visible stack ---- */
|
|
159
|
+
.bb-callwrap { display: flex; flex-direction: column; gap: 12px; }
|
|
160
|
+
/* extra top margin clears the pile edges that poke below the active card */
|
|
161
|
+
.bb-stacknav { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
|
|
162
|
+
.bb-stacknav__count { flex: 1 1 auto; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted); }
|
|
163
|
+
.bb-stacknav__btn {
|
|
164
|
+
display: inline-grid; place-items: center; width: 34px; height: 34px; cursor: pointer;
|
|
165
|
+
background: var(--surface-card); color: var(--text-strong);
|
|
166
|
+
border: 1.5px solid var(--border-default); border-radius: var(--radius-sm);
|
|
167
|
+
transition: background 120ms, border-color 120ms;
|
|
168
|
+
}
|
|
169
|
+
.bb-stacknav__btn:hover { background: var(--paper-100); border-color: var(--ink-500); }
|
|
170
|
+
.bb-stacknav__btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
|
|
171
|
+
.bb-stacknav__btn[disabled] { opacity: 0.4; cursor: not-allowed; }
|
|
172
|
+
.bb-stacknav__btn svg { width: 16px; height: 16px; }
|
|
173
|
+
/* the stack: a squared-up paper pile - solid card faces at slight opposing
|
|
174
|
+
rotations, like dealt cards, not translucent offset rectangles. The pile
|
|
175
|
+
thins as cards are answered: two edges, then one, then none on the last. */
|
|
176
|
+
.bb-call { position: relative; display: grid; }
|
|
177
|
+
.bb-call::before, .bb-call::after {
|
|
178
|
+
content: ""; position: absolute; inset: 0; z-index: 0;
|
|
179
|
+
background: var(--surface-card); border: 2px solid var(--ink-900);
|
|
180
|
+
border-radius: var(--radius-md); box-shadow: var(--shadow-hard-sm);
|
|
181
|
+
}
|
|
182
|
+
.bb-call::before { transform: rotate(1.3deg) translate(4px, 5px); }
|
|
183
|
+
.bb-call::after { transform: rotate(-1.1deg) translate(-3px, 9px); }
|
|
184
|
+
.bb-call--penult::after { display: none; }
|
|
185
|
+
.bb-call--last::before, .bb-call--last::after,
|
|
186
|
+
.bb-call--empty::before, .bb-call--empty::after { display: none; }
|
|
187
|
+
.bb-call > .bb-decision { grid-area: 1 / 1; z-index: 1; }
|
|
188
|
+
.bb-call > .bb-decision[hidden] { display: none; }
|
|
189
|
+
.bb-decision { display: flex; flex-direction: column; }
|
|
190
|
+
.bb-decision__pad { padding: 18px 20px 16px; display: flex; flex-direction: column; gap: 12px; flex: 1; }
|
|
191
|
+
.bb-decision__top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
192
|
+
.bb-decision__repo { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-faint); white-space: nowrap; }
|
|
193
|
+
.bb-decision__title { margin: 0; font-size: var(--fs-h4); font-weight: 800; color: var(--text-strong); line-height: 1.25; }
|
|
194
|
+
.bb-decision__detail { margin: 0; font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.45; }
|
|
195
|
+
/* about / decide / recommended - three scannable context rows */
|
|
196
|
+
.bb-ctx { display: flex; flex-direction: column; gap: 6px; }
|
|
197
|
+
.bb-ctx__row { display: grid; grid-template-columns: 78px minmax(0,1fr); gap: 8px; align-items: baseline; }
|
|
198
|
+
.bb-ctx__k { font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: 0.04em;
|
|
199
|
+
text-transform: uppercase; color: var(--text-faint); white-space: nowrap; }
|
|
200
|
+
.bb-ctx__v { font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.4; min-width: 0; }
|
|
201
|
+
.bb-decision__link { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--ocean-600); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
|
|
202
|
+
.bb-decision__link:hover { text-decoration: underline; }
|
|
203
|
+
/* One rhythm owner: the answer form is a flex column with the same 12px gap as
|
|
204
|
+
the card pad, and the foot rides margin-top:auto so every Queue answer button
|
|
205
|
+
sits on the same baseline with identical breathing room. */
|
|
206
|
+
.bb-decision form { display: flex; flex-direction: column; gap: 12px; flex: 1 1 auto; }
|
|
207
|
+
.bb-opts { display: flex; flex-direction: column; gap: 7px; }
|
|
208
|
+
.bb-opt {
|
|
209
|
+
display: flex; align-items: flex-start; gap: 10px; padding: 9px 12px; cursor: pointer;
|
|
210
|
+
background: var(--surface-card-warm); border: 1.5px solid var(--border-default);
|
|
211
|
+
border-radius: var(--radius-sm); transition: border-color 120ms, background 120ms;
|
|
212
|
+
}
|
|
213
|
+
.bb-opt:hover { border-color: var(--ink-300); }
|
|
214
|
+
.bb-opt input { accent-color: var(--rust-500); margin-top: 3px; flex: none; }
|
|
215
|
+
.bb-opt__body { min-width: 0; flex: 1 1 auto; }
|
|
216
|
+
.bb-opt__label { display: block; font-size: var(--fs-sm); font-weight: 700; color: var(--text-strong); line-height: 1.3; }
|
|
217
|
+
.bb-opt__hint { display: block; font-size: var(--fs-xs); color: var(--text-muted); line-height: 1.35; margin-top: 1px; }
|
|
218
|
+
.bb-opt__rec { flex: none; align-self: center; font-size: var(--fs-2xs); font-weight: 800; text-transform: uppercase;
|
|
219
|
+
letter-spacing: 0.07em; color: var(--navy-700); background: var(--gold-300);
|
|
220
|
+
border: 1px solid var(--gold-600); border-radius: var(--radius-xs); padding: 3px 7px 2px; }
|
|
221
|
+
.bb-opt:has(input:checked) { border-color: var(--rust-500); background: var(--rust-050); box-shadow: inset 0 0 0 1px var(--rust-500); }
|
|
222
|
+
.bb-decision__foot { display: flex; align-items: center; gap: 10px; margin-top: auto; }
|
|
223
|
+
.bb-queued {
|
|
224
|
+
display: none; align-items: center; gap: 6px;
|
|
225
|
+
font-size: var(--fs-2xs); font-weight: 800; text-transform: uppercase; letter-spacing: 0.07em;
|
|
226
|
+
color: var(--sea-700); background: var(--sea-050); border: 1.5px solid var(--sea-200);
|
|
227
|
+
border-radius: var(--radius-xs); padding: 5px 9px 4px;
|
|
228
|
+
}
|
|
229
|
+
.is-queued .bb-queued { display: inline-flex; }
|
|
230
|
+
.bb-limit { display: none; font-size: var(--fs-xs); font-weight: 700; color: var(--status-danger); }
|
|
231
|
+
.bb-limit.is-visible { display: inline; }
|
|
232
|
+
.bb-freeform { width: 100%; font-family: var(--font-sans); font-size: var(--fs-sm); color: var(--text-strong);
|
|
233
|
+
background: var(--surface-card); border: 1.5px solid var(--border-default); border-radius: var(--radius-sm); padding: 8px 11px; }
|
|
234
|
+
.bb-freeform:focus { outline: none; border-color: var(--rust-400); box-shadow: var(--focus-ring); }
|
|
235
|
+
|
|
236
|
+
/* ---- compact rows (Underway / Landed / Charted) ---- */
|
|
237
|
+
.bb-rows { display: flex; flex-direction: column; }
|
|
238
|
+
.bb-row {
|
|
239
|
+
display: flex; align-items: center; gap: 12px; padding: 11px 18px;
|
|
240
|
+
border-top: 1px solid var(--border-soft); min-width: 0;
|
|
241
|
+
}
|
|
242
|
+
.bb-row:first-child { border-top: none; }
|
|
243
|
+
.bb-row__main { min-width: 0; flex: 1 1 auto; }
|
|
244
|
+
.bb-row__title { font-size: var(--fs-sm); font-weight: 700; color: var(--text-strong); line-height: 1.3;
|
|
245
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
246
|
+
.bb-row__sub { font-size: var(--fs-xs); color: var(--text-muted); line-height: 1.3; margin-top: 1px;
|
|
247
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
248
|
+
.bb-row__id { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-faint); white-space: nowrap; }
|
|
249
|
+
.bb-row__pr { font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 600; color: var(--ocean-600); white-space: nowrap; }
|
|
250
|
+
.bb-row__pr:hover { text-decoration: underline; }
|
|
251
|
+
.bb-row__check { width: 26px; height: 26px; border-radius: var(--radius-sm); display: grid; place-items: center; flex: none;
|
|
252
|
+
background: var(--status-online-soft); color: var(--status-online); border: 1.5px solid var(--sea-200); }
|
|
253
|
+
.bb-row__check svg { width: 15px; height: 15px; }
|
|
254
|
+
.bb-morechip {
|
|
255
|
+
display: inline-flex; align-items: center; gap: 6px; margin: 10px 18px 12px;
|
|
256
|
+
font-size: var(--fs-xs); font-weight: 800; color: var(--text-muted);
|
|
257
|
+
border: 1.5px dashed var(--ink-300); border-radius: var(--radius-pill); padding: 5px 12px; width: fit-content;
|
|
258
|
+
}
|
|
259
|
+
.bb-empty { padding: 18px; font-size: var(--fs-sm); color: var(--text-muted); }
|
|
260
|
+
|
|
261
|
+
/* ---- Charted Next dispatch picker ---- */
|
|
262
|
+
/* margin:0 kills the UA default checkbox margin so picker rows and spacer rows
|
|
263
|
+
share one text left edge */
|
|
264
|
+
.bb-pick { accent-color: var(--rust-500); width: 16px; height: 16px; flex: none; cursor: pointer; margin: 0; }
|
|
265
|
+
.bb-pick-spacer { width: 16px; flex: none; }
|
|
266
|
+
.bb-dispatch {
|
|
267
|
+
display: flex; align-items: center; gap: 12px; padding: 12px 18px;
|
|
268
|
+
border-top: 1.5px solid var(--border-default); background: var(--paper-100);
|
|
269
|
+
}
|
|
270
|
+
.bb-dispatch__count { flex: 1 1 auto; font-size: var(--fs-xs); font-weight: 800; color: var(--text-muted);
|
|
271
|
+
text-transform: uppercase; letter-spacing: 0.07em; }
|
|
272
|
+
|
|
273
|
+
.bb-foot { display: flex; justify-content: flex-end; border-top: 1px solid var(--border-default); padding-top: 14px; }
|
|
274
|
+
|
|
275
|
+
@media (max-width: 900px) {
|
|
276
|
+
.bb-cols { grid-template-columns: 1fr; }
|
|
277
|
+
}
|
|
278
|
+
</style>
|
|
279
|
+
</head>
|
|
280
|
+
<body>
|
|
281
|
+
|
|
282
|
+
<header class="bb-nav">
|
|
283
|
+
<div class="bb-nav__inner">
|
|
284
|
+
<span class="bb-brand">
|
|
285
|
+
<span class="bb-brand__disc">
|
|
286
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
287
|
+
<path d="M12 6v16"/><path d="m19 13 2-1a9 9 0 0 1-18 0l2 1"/><path d="M9 11h6"/><circle cx="12" cy="4" r="2"/>
|
|
288
|
+
</svg>
|
|
289
|
+
</span>
|
|
290
|
+
<span class="bb-brand__wm">bearings</span>
|
|
291
|
+
</span>
|
|
292
|
+
</div>
|
|
293
|
+
</header>
|
|
294
|
+
|
|
295
|
+
<main class="bb-main">
|
|
296
|
+
|
|
297
|
+
<div class="bb-stats" id="bb-stats"></div>
|
|
298
|
+
|
|
299
|
+
<div class="bb-cols">
|
|
300
|
+
<section class="bb-section">
|
|
301
|
+
<div class="bb-section__head">
|
|
302
|
+
<span class="fm-sign fm-sign--eyebrow">
|
|
303
|
+
<svg class="fm-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>
|
|
304
|
+
Captain's Call
|
|
305
|
+
</span>
|
|
306
|
+
<span class="fm-sign fm-sign--muted" id="bb-call-sub"></span>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="bb-callwrap">
|
|
309
|
+
<div class="bb-call" id="bb-call"></div>
|
|
310
|
+
<!-- nav sits under the stack so the active card's top edge aligns with
|
|
311
|
+
the Charted Next card across the row -->
|
|
312
|
+
<div class="bb-stacknav">
|
|
313
|
+
<span class="bb-stacknav__count" id="bb-stack-count">-</span>
|
|
314
|
+
<button type="button" class="bb-stacknav__btn" id="bb-stack-prev" aria-label="Previous card">
|
|
315
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
|
|
316
|
+
</button>
|
|
317
|
+
<button type="button" class="bb-stacknav__btn" id="bb-stack-next" aria-label="Next card">
|
|
318
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
|
|
319
|
+
</button>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
</section>
|
|
323
|
+
|
|
324
|
+
<section class="bb-section">
|
|
325
|
+
<div class="bb-section__head">
|
|
326
|
+
<span class="fm-sign fm-sign--eyebrow">
|
|
327
|
+
<svg class="fm-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>
|
|
328
|
+
Charted Next
|
|
329
|
+
</span>
|
|
330
|
+
<span class="fm-sign fm-sign--muted" id="bb-charted-sub"></span>
|
|
331
|
+
</div>
|
|
332
|
+
<div class="fm-card fm-card--warm">
|
|
333
|
+
<div class="bb-rows" id="bb-charted"></div>
|
|
334
|
+
<div class="bb-dispatch" id="bb-dispatch" hidden>
|
|
335
|
+
<span class="bb-dispatch__count" id="bb-dispatch-count">nothing picked</span>
|
|
336
|
+
<span class="bb-queued" id="bb-dispatch-queued">
|
|
337
|
+
<svg class="fm-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
|
|
338
|
+
queued
|
|
339
|
+
</span>
|
|
340
|
+
<button type="button" class="fm-btn fm-btn--sm fm-btn--gold" id="bb-dispatch-btn">Queue dispatch order</button>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
</section>
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
<div class="bb-cols">
|
|
347
|
+
<section class="bb-section">
|
|
348
|
+
<div class="bb-section__head">
|
|
349
|
+
<span class="fm-sign fm-sign--eyebrow">
|
|
350
|
+
<svg class="fm-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 10.189V14"/><path d="M12 2v3"/><path d="M19 13V7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v6"/><path d="M19.38 20A11.6 11.6 0 0 0 21 14l-8.188-3.639a2 2 0 0 0-1.624 0L3 14a11.6 11.6 0 0 0 2.81 7.76"/><path d="M2 21c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1s1.2 1 2.5 1c2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/></svg>
|
|
351
|
+
Underway
|
|
352
|
+
</span>
|
|
353
|
+
</div>
|
|
354
|
+
<div class="fm-card"><div class="bb-rows" id="bb-underway"></div></div>
|
|
355
|
+
</section>
|
|
356
|
+
|
|
357
|
+
<section class="bb-section">
|
|
358
|
+
<div class="bb-section__head">
|
|
359
|
+
<span class="fm-sign fm-sign--eyebrow">
|
|
360
|
+
<svg class="fm-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/></svg>
|
|
361
|
+
Recently Landed
|
|
362
|
+
</span>
|
|
363
|
+
</div>
|
|
364
|
+
<div class="fm-card"><div class="bb-rows" id="bb-landed"></div></div>
|
|
365
|
+
</section>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
<footer class="bb-foot">
|
|
369
|
+
<span class="bb-meta-mono" id="bb-provenance">-</span>
|
|
370
|
+
</footer>
|
|
371
|
+
|
|
372
|
+
</main>
|
|
373
|
+
|
|
374
|
+
<!-- ONE SLOT. bin/fm-bearings-board.sh build injects the fresh
|
|
375
|
+
fm-bearings-board.v1 payload in place of the placeholder below; everything
|
|
376
|
+
above and below is the shipped template and is never regenerated per run. -->
|
|
377
|
+
<script id="bearings-data" type="application/json">
|
|
378
|
+
__FM_BEARINGS_BOARD_DATA__
|
|
379
|
+
</script>
|
|
380
|
+
|
|
381
|
+
<script>
|
|
382
|
+
(function () {
|
|
383
|
+
"use strict";
|
|
384
|
+
|
|
385
|
+
/* Fail closed: a board with unreadable or wrong-schema data says so plainly
|
|
386
|
+
instead of rendering an empty page that looks like an empty fleet. */
|
|
387
|
+
function renderBoardError(msg) {
|
|
388
|
+
var main = document.querySelector(".bb-main");
|
|
389
|
+
main.innerHTML = "";
|
|
390
|
+
var card = document.createElement("div");
|
|
391
|
+
card.className = "fm-card fm-card--poster bb-decision";
|
|
392
|
+
var pad = document.createElement("div");
|
|
393
|
+
pad.className = "bb-decision__pad";
|
|
394
|
+
var h = document.createElement("h3");
|
|
395
|
+
h.className = "bb-decision__title";
|
|
396
|
+
h.textContent = "This board could not load";
|
|
397
|
+
var d = document.createElement("p");
|
|
398
|
+
d.className = "bb-decision__detail";
|
|
399
|
+
d.textContent = msg + " - ask firstmate to rebuild the board.";
|
|
400
|
+
pad.appendChild(h);
|
|
401
|
+
pad.appendChild(d);
|
|
402
|
+
card.appendChild(pad);
|
|
403
|
+
main.appendChild(card);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
var data;
|
|
407
|
+
try {
|
|
408
|
+
data = JSON.parse(document.getElementById("bearings-data").textContent);
|
|
409
|
+
} catch (e) {
|
|
410
|
+
renderBoardError("The board data is not valid JSON");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (!data || data.schema !== "fm-bearings-board.v1") {
|
|
414
|
+
renderBoardError("Unsupported board data schema: " + (data && data.schema ? data.schema : "(none)"));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
var fleetLists = [data.captains_call, data.underway, data.landed, data.charted];
|
|
418
|
+
var callItemsValid = Array.isArray(data.captains_call) && data.captains_call.every(function (item) {
|
|
419
|
+
return item && typeof item === "object" &&
|
|
420
|
+
typeof item.key === "string" && item.key.length > 0 &&
|
|
421
|
+
typeof item.type === "string" && item.type.length > 0 &&
|
|
422
|
+
typeof item.title === "string" && item.title.length > 0 &&
|
|
423
|
+
Array.isArray(item.options);
|
|
424
|
+
});
|
|
425
|
+
if (!fleetLists.every(Array.isArray) || !callItemsValid) {
|
|
426
|
+
renderBoardError("The board data does not have the required structure");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
function el(tag, cls, text) {
|
|
432
|
+
var n = document.createElement(tag);
|
|
433
|
+
if (cls) n.className = cls;
|
|
434
|
+
if (text != null) n.textContent = text;
|
|
435
|
+
return n;
|
|
436
|
+
}
|
|
437
|
+
function badge(tone, text) { return el("span", "fm-badge fm-badge--" + tone, text); }
|
|
438
|
+
/* Warnings ride the charted feed for layout only; they are alarms, not work,
|
|
439
|
+
so every count of queued work excludes them. */
|
|
440
|
+
function isWarning(t) { return t && t.kind === "warning"; }
|
|
441
|
+
function chartedQueued(rows) { return (rows || []).filter(function (t) { return !isWarning(t); }); }
|
|
442
|
+
var chartedMoreQueued = data.charted_more || 0;
|
|
443
|
+
var chartedMoreWarnings = data.charted_warning_more || 0;
|
|
444
|
+
function utf8ByteLength(text) { return new TextEncoder().encode(text).length; }
|
|
445
|
+
var CHECK_SVG = '<svg class="fm-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
|
|
446
|
+
|
|
447
|
+
document.getElementById("bb-provenance").textContent = data.schema + " · " + data.generated;
|
|
448
|
+
|
|
449
|
+
/* stat strip */
|
|
450
|
+
var callTotal = data.captains_call.length;
|
|
451
|
+
var stats = [
|
|
452
|
+
{ n: callTotal, l: "need you", call: true },
|
|
453
|
+
{ n: data.underway.length, l: "underway" },
|
|
454
|
+
{ n: data.landed.length, l: "landed recently" },
|
|
455
|
+
{ n: chartedQueued(data.charted).length + chartedMoreQueued, l: "charted next" }
|
|
456
|
+
];
|
|
457
|
+
var strip = document.getElementById("bb-stats");
|
|
458
|
+
stats.forEach(function (s) {
|
|
459
|
+
var t = el("div", "bb-stat" + (s.call ? " bb-stat--call" : ""));
|
|
460
|
+
t.appendChild(el("span", "bb-stat__num", String(s.n)));
|
|
461
|
+
t.appendChild(el("span", "bb-stat__label", s.l));
|
|
462
|
+
strip.appendChild(t);
|
|
463
|
+
});
|
|
464
|
+
document.getElementById("bb-call-sub").textContent =
|
|
465
|
+
callTotal ? callTotal + " item" + (callTotal === 1 ? "" : "s") + " wait on you, captain" : "nothing needs you, captain";
|
|
466
|
+
|
|
467
|
+
/* Captain's Call deck: every item renders as the same full card */
|
|
468
|
+
var deck = document.getElementById("bb-call");
|
|
469
|
+
function ctxRow(k, v, rec) {
|
|
470
|
+
var row = el("div", "bb-ctx__row" + (rec ? " bb-ctx__row--rec" : ""));
|
|
471
|
+
row.appendChild(el("span", "bb-ctx__k", k));
|
|
472
|
+
row.appendChild(el("span", "bb-ctx__v", v));
|
|
473
|
+
return row;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
data.captains_call.forEach(function (item) {
|
|
477
|
+
var card = el("div", "fm-card fm-card--poster bb-decision");
|
|
478
|
+
var pad = el("div", "bb-decision__pad");
|
|
479
|
+
|
|
480
|
+
var top = el("div", "bb-decision__top");
|
|
481
|
+
if (item.type === "merge") {
|
|
482
|
+
top.appendChild(badge("online", "checks green"));
|
|
483
|
+
top.appendChild(badge(item.risk === "low" ? "neutral" : "warn", "risk " + item.risk));
|
|
484
|
+
} else {
|
|
485
|
+
top.appendChild(badge("solid", "decision"));
|
|
486
|
+
top.appendChild(el("span", "bb-decision__repo", item.repo));
|
|
487
|
+
}
|
|
488
|
+
pad.appendChild(top);
|
|
489
|
+
|
|
490
|
+
pad.appendChild(el("h3", "bb-decision__title", item.title));
|
|
491
|
+
|
|
492
|
+
if (item.type === "merge") {
|
|
493
|
+
if (item.detail) pad.appendChild(el("p", "bb-decision__detail", item.detail));
|
|
494
|
+
if (item.pr_url) {
|
|
495
|
+
var pr = el("a", "bb-decision__link", item.pr_url);
|
|
496
|
+
pr.href = item.pr_url; pr.target = "_blank"; pr.rel = "noopener";
|
|
497
|
+
pad.appendChild(pr);
|
|
498
|
+
}
|
|
499
|
+
} else {
|
|
500
|
+
/* the rec marker on the option itself carries the recommendation */
|
|
501
|
+
var ctx = el("div", "bb-ctx");
|
|
502
|
+
if (item.about) ctx.appendChild(ctxRow("about", item.about));
|
|
503
|
+
if (item.decide) ctx.appendChild(ctxRow("decide", item.decide));
|
|
504
|
+
pad.appendChild(ctx);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
var form = document.createElement("form");
|
|
508
|
+
form.setAttribute("data-lavish-question", item.key);
|
|
509
|
+
var opts = el("div", "bb-opts");
|
|
510
|
+
item.options.forEach(function (o) {
|
|
511
|
+
var lab = el("label", "bb-opt");
|
|
512
|
+
var input = document.createElement("input");
|
|
513
|
+
input.type = "radio"; input.name = "answer"; input.value = o.value;
|
|
514
|
+
lab.appendChild(input);
|
|
515
|
+
var body = el("span", "bb-opt__body");
|
|
516
|
+
body.appendChild(el("span", "bb-opt__label", o.label));
|
|
517
|
+
if (o.hint) body.appendChild(el("span", "bb-opt__hint", o.hint));
|
|
518
|
+
lab.appendChild(body);
|
|
519
|
+
if (item.recommend_value && o.value === item.recommend_value) {
|
|
520
|
+
lab.appendChild(el("span", "bb-opt__rec", "rec"));
|
|
521
|
+
}
|
|
522
|
+
opts.appendChild(lab);
|
|
523
|
+
});
|
|
524
|
+
form.appendChild(opts);
|
|
525
|
+
|
|
526
|
+
if (item.allow_freeform) {
|
|
527
|
+
var ff = document.createElement("input");
|
|
528
|
+
ff.type = "text"; ff.name = "note"; ff.className = "bb-freeform";
|
|
529
|
+
ff.placeholder = item.freeform_hint || "or answer in your own words…";
|
|
530
|
+
form.appendChild(ff);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
var foot = el("div", "bb-decision__foot");
|
|
534
|
+
var btn = el("button", "fm-btn fm-btn--sm " + (item.type === "merge" ? "fm-btn--gold" : "fm-btn--primary"), "Queue answer");
|
|
535
|
+
btn.type = "submit";
|
|
536
|
+
foot.appendChild(btn);
|
|
537
|
+
var q = el("span", "bb-queued");
|
|
538
|
+
q.innerHTML = CHECK_SVG + " queued";
|
|
539
|
+
foot.appendChild(q);
|
|
540
|
+
var answerLimit = el("span", "bb-limit");
|
|
541
|
+
answerLimit.setAttribute("role", "alert");
|
|
542
|
+
foot.appendChild(answerLimit);
|
|
543
|
+
form.appendChild(foot);
|
|
544
|
+
|
|
545
|
+
form.addEventListener("submit", function (ev) {
|
|
546
|
+
ev.preventDefault();
|
|
547
|
+
answerLimit.classList.remove("is-visible");
|
|
548
|
+
var fd = new FormData(form);
|
|
549
|
+
var value = fd.get("answer");
|
|
550
|
+
var note = (fd.get("note") || "").trim();
|
|
551
|
+
/* picked option, optionally annotated; a bare note is itself the answer */
|
|
552
|
+
var answer = value ? (note ? value + " - " + note : value) : note;
|
|
553
|
+
if (!answer) return;
|
|
554
|
+
if (utf8ByteLength(answer) > 512) {
|
|
555
|
+
answerLimit.textContent = "Answer is too long to queue (512 bytes maximum).";
|
|
556
|
+
answerLimit.classList.add("is-visible");
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (window.lavish && window.lavish.queuePrompt) {
|
|
560
|
+
/* close carries the composer-declared close mode: "release" frees a
|
|
561
|
+
captain-gated work item instead of completing a question task */
|
|
562
|
+
var ctxData = { question: item.key, answer: answer };
|
|
563
|
+
if (item.close) ctxData.close = item.close;
|
|
564
|
+
window.lavish.queuePrompt(
|
|
565
|
+
"Captain's Call answer - " + item.title + ": " + answer,
|
|
566
|
+
{ tag: "choice", text: item.title + " -> " + answer, element: form,
|
|
567
|
+
data: ctxData }
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
card.classList.add("is-queued");
|
|
571
|
+
/* deal the next card once this one is answered */
|
|
572
|
+
setTimeout(function () { showCard(active < cards.length - 1 ? active + 1 : active); }, 450);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
pad.appendChild(form);
|
|
576
|
+
card.appendChild(pad);
|
|
577
|
+
deck.appendChild(card);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
/* stack navigation: one card at a time, dealt off the pile */
|
|
581
|
+
var cards = Array.prototype.slice.call(deck.children);
|
|
582
|
+
var active = 0;
|
|
583
|
+
var stackCount = document.getElementById("bb-stack-count");
|
|
584
|
+
var stackPrev = document.getElementById("bb-stack-prev");
|
|
585
|
+
var stackNext = document.getElementById("bb-stack-next");
|
|
586
|
+
var stackNav = stackCount.parentNode;
|
|
587
|
+
function showCard(i) {
|
|
588
|
+
active = Math.max(0, Math.min(cards.length - 1, i));
|
|
589
|
+
cards.forEach(function (c, j) { c.hidden = j !== active; });
|
|
590
|
+
var answered = deck.querySelectorAll(".is-queued").length;
|
|
591
|
+
stackCount.textContent = "card " + (active + 1) + " of " + cards.length +
|
|
592
|
+
(answered ? " · " + answered + " answered" : "");
|
|
593
|
+
stackPrev.disabled = active === 0;
|
|
594
|
+
stackNext.disabled = active === cards.length - 1;
|
|
595
|
+
/* the pile thins as the deal approaches the bottom */
|
|
596
|
+
deck.classList.toggle("bb-call--penult", active === cards.length - 2);
|
|
597
|
+
deck.classList.toggle("bb-call--last", active === cards.length - 1);
|
|
598
|
+
}
|
|
599
|
+
if (cards.length) {
|
|
600
|
+
stackPrev.addEventListener("click", function () { showCard(active - 1); });
|
|
601
|
+
stackNext.addEventListener("click", function () { showCard(active + 1); });
|
|
602
|
+
showCard(0);
|
|
603
|
+
} else {
|
|
604
|
+
deck.classList.add("bb-call--empty");
|
|
605
|
+
deck.appendChild(el("div", "bb-empty", "Nothing needs your action right now."));
|
|
606
|
+
stackNav.hidden = true;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/* Underway */
|
|
610
|
+
var uw = document.getElementById("bb-underway");
|
|
611
|
+
if (!data.underway.length) uw.appendChild(el("div", "bb-empty", "Nothing is underway."));
|
|
612
|
+
data.underway.forEach(function (t) {
|
|
613
|
+
var row = el("div", "bb-row");
|
|
614
|
+
row.appendChild(badge(t.state === "working" ? "online" : "info", t.state));
|
|
615
|
+
var main = el("div", "bb-row__main");
|
|
616
|
+
main.appendChild(el("div", "bb-row__title", t.doing));
|
|
617
|
+
/* captain-facing rows name the repo; the internal task id shows only when
|
|
618
|
+
no repo is known */
|
|
619
|
+
main.appendChild(el("div", "bb-row__sub", t.kind + " · " + (t.repo || t.id)));
|
|
620
|
+
row.appendChild(main);
|
|
621
|
+
uw.appendChild(row);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
/* Landed */
|
|
625
|
+
var ld = document.getElementById("bb-landed");
|
|
626
|
+
if (!data.landed.length) ld.appendChild(el("div", "bb-empty", "No recent completions are in the current baseline."));
|
|
627
|
+
data.landed.forEach(function (t) {
|
|
628
|
+
var row = el("div", "bb-row");
|
|
629
|
+
var chk = el("span", "bb-row__check");
|
|
630
|
+
chk.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
|
|
631
|
+
row.appendChild(chk);
|
|
632
|
+
var main = el("div", "bb-row__main");
|
|
633
|
+
main.appendChild(el("div", "bb-row__title", t.what));
|
|
634
|
+
main.appendChild(el("div", "bb-row__sub", (t.repo || t.id) + " · " + t.owner));
|
|
635
|
+
row.appendChild(main);
|
|
636
|
+
if (t.pr_url) {
|
|
637
|
+
var a = el("a", "bb-row__pr", "#" + t.pr_url.split("/").pop());
|
|
638
|
+
a.href = t.pr_url; a.title = t.pr_url; a.target = "_blank"; a.rel = "noopener";
|
|
639
|
+
row.appendChild(a);
|
|
640
|
+
}
|
|
641
|
+
ld.appendChild(row);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
/* Charted Next + dispatch picker */
|
|
645
|
+
var ch = document.getElementById("bb-charted");
|
|
646
|
+
var bar = document.getElementById("bb-dispatch");
|
|
647
|
+
var barCount = document.getElementById("bb-dispatch-count");
|
|
648
|
+
var barBtn = document.getElementById("bb-dispatch-btn");
|
|
649
|
+
var anyPickable = false;
|
|
650
|
+
|
|
651
|
+
function pickedIds() {
|
|
652
|
+
return Array.prototype.map.call(ch.querySelectorAll(".bb-pick:checked"), function (c) { return c.value; });
|
|
653
|
+
}
|
|
654
|
+
function refreshBar(limitMessage) {
|
|
655
|
+
var n = pickedIds().length;
|
|
656
|
+
barCount.textContent = limitMessage || (n ? n + " picked for dispatch" : "pick queued work to dispatch");
|
|
657
|
+
barBtn.disabled = !n;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (!chartedQueued(data.charted).length && !chartedMoreQueued) {
|
|
661
|
+
ch.appendChild(el("div", "bb-empty", "Nothing is queued."));
|
|
662
|
+
}
|
|
663
|
+
data.charted.forEach(function (t) {
|
|
664
|
+
var row = el("div", "bb-row");
|
|
665
|
+
if (t.dispatchable && !isWarning(t)) {
|
|
666
|
+
anyPickable = true;
|
|
667
|
+
var pick = document.createElement("input");
|
|
668
|
+
pick.type = "checkbox"; pick.className = "bb-pick"; pick.value = t.id;
|
|
669
|
+
pick.setAttribute("aria-label", "Pick " + t.id + " for dispatch");
|
|
670
|
+
pick.addEventListener("change", function () {
|
|
671
|
+
if (pick.checked && utf8ByteLength(pickedIds().join(",")) > 512) {
|
|
672
|
+
pick.checked = false;
|
|
673
|
+
refreshBar("Selection limit reached (512 bytes maximum). ");
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
refreshBar();
|
|
677
|
+
});
|
|
678
|
+
row.appendChild(pick);
|
|
679
|
+
} else {
|
|
680
|
+
row.appendChild(el("span", "bb-pick-spacer"));
|
|
681
|
+
}
|
|
682
|
+
var main = el("div", "bb-row__main");
|
|
683
|
+
main.appendChild(el("div", "bb-row__title", t.title));
|
|
684
|
+
/* second line keeps the title uncrowded in the half-width column */
|
|
685
|
+
var chSub = t.repo || t.id;
|
|
686
|
+
main.appendChild(el("div", "bb-row__sub", t.reason ? t.reason + " · " + chSub : chSub));
|
|
687
|
+
row.appendChild(main);
|
|
688
|
+
if (isWarning(t)) row.appendChild(badge("danger", "needs repair"));
|
|
689
|
+
else if (t.reason) row.appendChild(badge("warn", "waiting"));
|
|
690
|
+
ch.appendChild(row);
|
|
691
|
+
});
|
|
692
|
+
var chartedShown = chartedQueued(data.charted).length;
|
|
693
|
+
var chartedTotal = chartedShown + chartedMoreQueued;
|
|
694
|
+
document.getElementById("bb-charted-sub").textContent =
|
|
695
|
+
chartedMoreQueued ? "showing " + chartedShown + " of " + chartedTotal : "";
|
|
696
|
+
if (chartedMoreQueued) {
|
|
697
|
+
ch.appendChild(el("span", "bb-morechip", "+" + chartedMoreQueued + " more queued - ask firstmate for the full chart"));
|
|
698
|
+
}
|
|
699
|
+
if (chartedMoreWarnings) {
|
|
700
|
+
ch.appendChild(el("span", "bb-morechip", "+" + chartedMoreWarnings + " more repair warning" + (chartedMoreWarnings === 1 ? "" : "s") + " - ask firstmate for the full chart"));
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (anyPickable) {
|
|
704
|
+
bar.hidden = false;
|
|
705
|
+
refreshBar();
|
|
706
|
+
barBtn.addEventListener("click", function () {
|
|
707
|
+
var ids = pickedIds();
|
|
708
|
+
var dispatchAnswer = ids.join(",");
|
|
709
|
+
if (!ids.length) return;
|
|
710
|
+
if (utf8ByteLength(dispatchAnswer) > 512) {
|
|
711
|
+
refreshBar("Selection is too long to queue (512 bytes maximum). ");
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (window.lavish && window.lavish.queuePrompt) {
|
|
715
|
+
window.lavish.queuePrompt(
|
|
716
|
+
"Dispatch order - start this queued work now: " + ids.join(", "),
|
|
717
|
+
{ tag: "choice", text: "Dispatch: " + ids.join(", "), element: bar,
|
|
718
|
+
queueKey: "dispatch.charted",
|
|
719
|
+
data: { question: "dispatch.charted", answer: dispatchAnswer } }
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
bar.classList.add("is-queued");
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
} catch (e) {
|
|
726
|
+
renderBoardError("The board data could not be rendered");
|
|
727
|
+
}
|
|
728
|
+
})();
|
|
729
|
+
</script>
|
|
730
|
+
</body>
|
|
731
|
+
</html>
|