@starlove/orbit 1.1.2 → 1.1.11

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.
Files changed (103) hide show
  1. package/.dockerignore +0 -0
  2. package/Dockerfile +0 -0
  3. package/LICENSE +0 -0
  4. package/README.md +0 -0
  5. package/SKILL-ORBIT.md +2 -0
  6. package/build.mjs +0 -0
  7. package/dist/full/apple-touch-icon.png +0 -0
  8. package/dist/full/cursors/live_arrow.cur +0 -0
  9. package/dist/full/cursors/live_link.cur +0 -0
  10. package/dist/full/cursors/live_move.cur +0 -0
  11. package/dist/full/cursors/live_select.cur +0 -0
  12. package/dist/full/cursors/live_unavail.cur +0 -0
  13. package/dist/full/favicon-32x32.png +0 -0
  14. package/dist/full/index.html +160 -15
  15. package/dist/full/js/app.55b38e12.js +698 -0
  16. package/dist/full/js/app.55b38e12.js.map +7 -0
  17. package/dist/full/orbits-favicon.png +0 -0
  18. package/dist/full/orbits-logo-bg.png +0 -0
  19. package/dist/full/orbits-logo.png +0 -0
  20. package/dist/full/{styles.836872e7.css → styles.273fc37a.css} +79 -39
  21. package/dist/full/version.json +2 -2
  22. package/docs/AGENT_PROTOCOL.md +0 -0
  23. package/docs/MCP_SETUP.md +0 -0
  24. package/docs/ORBIT_DISPATCH.md +68 -8
  25. package/docs/SS-Orbit-Dark.png +0 -0
  26. package/docs/SS-Orbit-Light.png +0 -0
  27. package/docs/mcp-client.example.json +0 -0
  28. package/docs/setup/orbit-init-wizard.md +0 -0
  29. package/package.json +1 -1
  30. package/public/apple-touch-icon.png +0 -0
  31. package/public/cursors/live_arrow.cur +0 -0
  32. package/public/cursors/live_link.cur +0 -0
  33. package/public/cursors/live_move.cur +0 -0
  34. package/public/cursors/live_select.cur +0 -0
  35. package/public/cursors/live_unavail.cur +0 -0
  36. package/public/favicon-32x32.png +0 -0
  37. package/public/index.html +158 -13
  38. package/public/js/actor-labels.js +0 -0
  39. package/public/js/api.js +0 -0
  40. package/public/js/app.js +0 -0
  41. package/public/js/board-menu.js +0 -0
  42. package/public/js/card-actions.js +19 -0
  43. package/public/js/config.js +0 -0
  44. package/public/js/create-card.js +0 -0
  45. package/public/js/dom.js +0 -0
  46. package/public/js/drawer.js +17 -43
  47. package/public/js/format.js +5 -5
  48. package/public/js/kanban.js +157 -92
  49. package/public/js/local-backend.js +0 -0
  50. package/public/js/main.js +2 -1
  51. package/public/js/modal.js +0 -0
  52. package/public/js/motion-preference.js +14 -3
  53. package/public/js/router.js +0 -0
  54. package/public/js/search.js +0 -0
  55. package/public/js/settings-tabs.js +0 -0
  56. package/public/js/settings.js +131 -27
  57. package/public/js/sse.js +0 -0
  58. package/public/js/starscape.js +0 -0
  59. package/public/js/state.js +0 -0
  60. package/public/js/theme-preference.js +0 -0
  61. package/public/js/ticket-detail.js +88 -20
  62. package/public/js/toast.js +0 -0
  63. package/public/js/transport.js +0 -0
  64. package/public/js/unread.js +0 -0
  65. package/public/js/url-routes.js +0 -0
  66. package/public/orbits-favicon.png +0 -0
  67. package/public/orbits-logo-bg.png +0 -0
  68. package/public/orbits-logo.png +0 -0
  69. package/public/styles.css +79 -39
  70. package/src/cli/orbit.js +22 -0
  71. package/src/core/agent.js +0 -0
  72. package/src/core/agents-md.js +0 -0
  73. package/src/core/attachments.js +0 -0
  74. package/src/core/auth.js +0 -0
  75. package/src/core/backups.js +0 -0
  76. package/src/core/board.js +1 -0
  77. package/src/core/boards.js +0 -0
  78. package/src/core/bootstrap.js +0 -0
  79. package/src/core/db.js +19 -0
  80. package/src/core/delete-board.js +0 -0
  81. package/src/core/dispatch.js +176 -11
  82. package/src/core/events.js +0 -0
  83. package/src/core/http.js +0 -0
  84. package/src/core/migrate.js +0 -0
  85. package/src/core/paths.js +0 -0
  86. package/src/core/provision-repo-board.js +0 -0
  87. package/src/core/queries.js +0 -0
  88. package/src/core/registry.js +2 -2
  89. package/src/core/relations.js +0 -0
  90. package/src/core/review-verdicts.js +138 -0
  91. package/src/core/router.js +32 -3
  92. package/src/core/search.js +0 -0
  93. package/src/core/seed.js +0 -0
  94. package/src/core/snapshots.js +429 -9
  95. package/src/core/states.js +0 -0
  96. package/src/core/system-picker.js +0 -0
  97. package/src/core/tickets.js +0 -0
  98. package/src/core/util.js +0 -0
  99. package/src/mcp/orbit-client.js +45 -19
  100. package/src/mcp-server.js +102 -27
  101. package/src/server.js +0 -0
  102. package/dist/full/js/app.a4f045fc.js +0 -676
  103. package/dist/full/js/app.a4f045fc.js.map +0 -7
File without changes
File without changes
File without changes
@@ -321,7 +321,7 @@ textarea:focus {
321
321
  }
322
322
 
323
323
  @media (prefers-reduced-motion: reduce) {
324
- [data-theme="dark"] .topbar {
324
+ :root:not([data-motion="full"])[data-theme="dark"] .topbar {
325
325
  transition: none;
326
326
  }
327
327
  }
@@ -1037,8 +1037,8 @@ h1 {
1037
1037
  }
1038
1038
 
1039
1039
  @media (prefers-reduced-motion: reduce) {
1040
- .create-flyout,
1041
- .create-flyout-backdrop {
1040
+ :root:not([data-motion="full"]) .create-flyout,
1041
+ :root:not([data-motion="full"]) .create-flyout-backdrop {
1042
1042
  transition: none;
1043
1043
  }
1044
1044
  }
@@ -1347,7 +1347,7 @@ h1 {
1347
1347
  }
1348
1348
 
1349
1349
  @media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: reduce) {
1350
- .add-card-phantom {
1350
+ :root:not([data-motion="full"]) .add-card-phantom {
1351
1351
  transition:
1352
1352
  color 160ms ease,
1353
1353
  border-color 160ms ease,
@@ -1402,7 +1402,7 @@ h1 {
1402
1402
  }
1403
1403
 
1404
1404
  @media (prefers-reduced-motion: reduce) {
1405
- .agent-dot {
1405
+ :root:not([data-motion="full"]) .agent-dot {
1406
1406
  animation: none;
1407
1407
  }
1408
1408
  }
@@ -1443,7 +1443,7 @@ h1 {
1443
1443
  }
1444
1444
 
1445
1445
  @media (prefers-reduced-motion: reduce) {
1446
- .card-unread-dot {
1446
+ :root:not([data-motion="full"]) .card-unread-dot {
1447
1447
  animation: none;
1448
1448
  }
1449
1449
  }
@@ -1691,7 +1691,7 @@ h1 {
1691
1691
  }
1692
1692
 
1693
1693
  @media (prefers-reduced-motion: reduce) {
1694
- .card {
1694
+ :root:not([data-motion="full"]) .card {
1695
1695
  animation: none;
1696
1696
  }
1697
1697
  }
@@ -1823,8 +1823,10 @@ h1 {
1823
1823
  [data-theme="dark"] .card {
1824
1824
  border-color: rgba(var(--starlight-rgb),0.12);
1825
1825
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25);
1826
- backdrop-filter: blur(16px);
1827
- -webkit-backdrop-filter: blur(16px);
1826
+ /* No backdrop-filter: --card is 85% opaque so the blur showed through ~15%
1827
+ (near-invisible) yet forced every card to re-blur the animated starfield
1828
+ behind it on every frame — the page's biggest GPU cost. The opaque fill
1829
+ carries the surface; the starfield still animates in the gaps. */
1828
1830
  }
1829
1831
 
1830
1832
  [data-theme="dark"] .card.type-epic {
@@ -2134,7 +2136,7 @@ h1 {
2134
2136
  }
2135
2137
 
2136
2138
  @media (prefers-reduced-motion: reduce) {
2137
- .priority-pill-urgent {
2139
+ :root:not([data-motion="full"]) .priority-pill-urgent {
2138
2140
  animation: none;
2139
2141
  }
2140
2142
  }
@@ -2165,11 +2167,17 @@ h1 {
2165
2167
  }
2166
2168
 
2167
2169
  @media (prefers-reduced-motion: no-preference) {
2168
- [data-theme="dark"] .priority-pill-urgent {
2170
+ :root:not([data-motion="reduce"])[data-theme="dark"] .priority-pill-urgent {
2169
2171
  box-shadow: 0 0 0 1px rgba(var(--coral-rgb),0.35), 0 4px 10px rgba(var(--coral-rgb),0.3);
2170
2172
  }
2171
2173
  }
2172
2174
 
2175
+ /* Full-motion escape hatch: show the urgent glow even when the OS prefers
2176
+ reduced motion but the user forced full motion. */
2177
+ :root[data-motion="full"][data-theme="dark"] .priority-pill-urgent {
2178
+ box-shadow: 0 0 0 1px rgba(var(--coral-rgb),0.35), 0 4px 10px rgba(var(--coral-rgb),0.3);
2179
+ }
2180
+
2173
2181
  .card-expand-trigger {
2174
2182
  display: flex;
2175
2183
  align-items: center;
@@ -2339,8 +2347,8 @@ h1 {
2339
2347
  }
2340
2348
 
2341
2349
  @media (prefers-reduced-motion: reduce) {
2342
- .drawer,
2343
- .drawer-backdrop {
2350
+ :root:not([data-motion="full"]) .drawer,
2351
+ :root:not([data-motion="full"]) .drawer-backdrop {
2344
2352
  transition: none;
2345
2353
  }
2346
2354
  }
@@ -3671,16 +3679,13 @@ details.section > summary .mcp-title-row {
3671
3679
  min-height: 2rem;
3672
3680
  }
3673
3681
 
3674
- /* Board Notes is the long-form home for project guidance; reserve a textarea
3675
- sized landing area so it doesn't collapse to a one-liner when empty. */
3676
- .settings-notes-body {
3682
+ /* Long-form settings fields reserve textarea-sized landing areas so they don't
3683
+ collapse to one-liners when empty. */
3684
+ .settings-notes-body,
3685
+ .settings-agent-instructions-body {
3677
3686
  min-height: 11rem;
3678
3687
  }
3679
3688
 
3680
- .ai-context-setup textarea[name="agent_instructions"] {
3681
- min-height: 9rem;
3682
- }
3683
-
3684
3689
  .ai-context-setup > .description {
3685
3690
  margin-bottom: 0.7rem;
3686
3691
  }
@@ -5170,8 +5175,8 @@ button:disabled,
5170
5175
  .toast-error .toast-icon { color: #ef4444; }
5171
5176
 
5172
5177
  @media (prefers-reduced-motion: reduce) {
5173
- .toast-enter { animation: none; opacity: 1; transform: none; }
5174
- .toast-exit { animation: none; }
5178
+ :root:not([data-motion="full"]) .toast-enter { animation: none; opacity: 1; transform: none; }
5179
+ :root:not([data-motion="full"]) .toast-exit { animation: none; }
5175
5180
  }
5176
5181
 
5177
5182
  [data-theme="dark"] .toast {
@@ -5332,8 +5337,8 @@ button:disabled,
5332
5337
  }
5333
5338
 
5334
5339
  @media (prefers-reduced-motion: reduce) {
5335
- .orbit-dialog,
5336
- .orbit-dialog::backdrop {
5340
+ :root:not([data-motion="full"]) .orbit-dialog,
5341
+ :root:not([data-motion="full"]) .orbit-dialog::backdrop {
5337
5342
  animation: none;
5338
5343
  }
5339
5344
  }
@@ -5669,40 +5674,75 @@ button:disabled,
5669
5674
  }
5670
5675
  }
5671
5676
 
5677
+ /* Reduced motion — Starscape v3 `data-motion` contract (migrated from the
5678
+ * legacy `data-reduced-motion="reduce|allow"`):
5679
+ * • OS path: reduce when the OS asks, unless the user forces full motion.
5680
+ * • Explicit path: `data-motion="reduce"` reduces regardless of OS.
5681
+ * • `data-motion="full"` opts out of the OS path (full motion even on OS reduce). */
5672
5682
  @media (prefers-reduced-motion: reduce) {
5673
- [data-theme="dark"] .starfield,
5674
- [data-theme="dark"] .milky-way,
5675
- [data-theme="dark"] .grain::before,
5676
- [data-theme="dark"] .grain::after,
5677
- [data-theme="dark"] .signature-star {
5683
+ :root:not([data-motion="full"]) *,
5684
+ :root:not([data-motion="full"]) *::before,
5685
+ :root:not([data-motion="full"]) *::after {
5686
+ scroll-behavior: auto !important;
5687
+ transition-duration: 0.01ms !important;
5688
+ animation-duration: 0.01ms !important;
5689
+ animation-iteration-count: 1 !important;
5690
+ }
5691
+
5692
+ :root:not([data-motion="full"])[data-theme="dark"] .starfield,
5693
+ :root:not([data-motion="full"])[data-theme="dark"] .milky-way,
5694
+ :root:not([data-motion="full"])[data-theme="dark"] .grain::before,
5695
+ :root:not([data-motion="full"])[data-theme="dark"] .grain::after,
5696
+ :root:not([data-motion="full"])[data-theme="dark"] .signature-star {
5678
5697
  animation: none;
5679
5698
  }
5680
- [data-theme="dark"] .meteor {
5699
+
5700
+ :root:not([data-motion="full"])[data-theme="dark"] .meteor {
5681
5701
  display: none;
5682
5702
  }
5683
5703
  }
5684
5704
 
5685
- [data-reduced-motion="reduce"] *,
5686
- [data-reduced-motion="reduce"] *::before,
5687
- [data-reduced-motion="reduce"] *::after {
5705
+ [data-motion="reduce"] *,
5706
+ [data-motion="reduce"] *::before,
5707
+ [data-motion="reduce"] *::after {
5688
5708
  scroll-behavior: auto !important;
5689
5709
  transition-duration: 0.01ms !important;
5690
5710
  animation-duration: 0.01ms !important;
5691
5711
  animation-iteration-count: 1 !important;
5692
5712
  }
5693
5713
 
5694
- [data-reduced-motion="reduce"][data-theme="dark"] .starfield,
5695
- [data-reduced-motion="reduce"][data-theme="dark"] .milky-way,
5696
- [data-reduced-motion="reduce"][data-theme="dark"] .grain::before,
5697
- [data-reduced-motion="reduce"][data-theme="dark"] .grain::after,
5698
- [data-reduced-motion="reduce"][data-theme="dark"] .signature-star {
5714
+ [data-motion="reduce"][data-theme="dark"] .starfield,
5715
+ [data-motion="reduce"][data-theme="dark"] .milky-way,
5716
+ [data-motion="reduce"][data-theme="dark"] .grain::before,
5717
+ [data-motion="reduce"][data-theme="dark"] .grain::after,
5718
+ [data-motion="reduce"][data-theme="dark"] .signature-star {
5699
5719
  animation: none;
5700
5720
  }
5701
5721
 
5702
- [data-reduced-motion="reduce"][data-theme="dark"] .meteor {
5722
+ [data-motion="reduce"][data-theme="dark"] .meteor {
5703
5723
  display: none;
5704
5724
  }
5705
5725
 
5726
+ /* Empty-lane icon micro-motion — a low-amplitude exception to the OS reduced-
5727
+ * motion reset above. These are small (64px), slow, localized loops, not the
5728
+ * large-area/parallax motion that triggers vestibular discomfort, so they
5729
+ * survive an *OS-level* reduced-motion preference. They are still fully stopped
5730
+ * by an *explicit* in-app choice (data-motion="reduce") — a deliberate "calm it
5731
+ * down" wins over the ambient OS default. The !important + selector specificity
5732
+ * is what overrides the universal `* { animation-duration: 0.01ms !important }`
5733
+ * reset for just these elements. */
5734
+ @media (prefers-reduced-motion: reduce) {
5735
+ :root:not([data-motion="reduce"]) .empty-state-icon.star {
5736
+ animation: star-pulse 4s ease-in-out infinite !important;
5737
+ }
5738
+ :root:not([data-motion="reduce"]) .empty-state-icon.ai-sparkle {
5739
+ animation: ai-float 6s ease-in-out infinite !important;
5740
+ }
5741
+ :root:not([data-motion="reduce"]) .empty-state-icon.orbit .satellite {
5742
+ animation: orbit-rotate 10s linear infinite !important;
5743
+ }
5744
+ }
5745
+
5706
5746
  [data-theme="dark"] {
5707
5747
  /* Tells the browser to render native widgets (dropdowns, scrollbars,
5708
5748
  date pickers, etc.) in their dark variant so option popups match. */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starlove/orbit",
3
- "version": "1.1.2",
3
+ "version": "1.1.11",
4
4
  "edition": "full",
5
- "builtAt": "2026-05-26T06:59:10.275Z"
5
+ "builtAt": "2026-05-29T22:34:33.369Z"
6
6
  }
File without changes
package/docs/MCP_SETUP.md CHANGED
File without changes
@@ -44,6 +44,11 @@ orbit dispatch --board my-app --ticket 12 --profile agent --worktree --foregroun
44
44
  orbit dispatch --board my-app --ticket 12 --profile agent --worktree \
45
45
  --branch orbit/my-app-12-search \
46
46
  --worktree-path .worktrees/my-app-12-search
47
+
48
+ # Declare ticket-specific verification expectations in the handoff/run record.
49
+ orbit dispatch --board my-app --ticket 12 --profile agent --worktree \
50
+ --verify-command "node --test test/orbit-cli.test.mjs" \
51
+ --verify-command "git diff --check"
47
52
  ```
48
53
 
49
54
  ## Preflight order
@@ -55,8 +60,9 @@ Before any filesystem writes, worktree creation, ticket mutation, or agent spawn
55
60
  3. Resolves the ticket from `--ticket` by number or id.
56
61
  4. Refuses archived tickets.
57
62
  5. Refuses blocked tickets unless `--force` is passed.
58
- 6. For spawn mode, checks that the Hermes binary exists and the requested profile is plausible.
59
- 7. Handles `--dry-run` as a true no-write preview.
63
+ 6. Rejects missing or flag-looking `--verify-command` values during CLI argument parsing.
64
+ 7. For spawn mode, checks that the Hermes binary exists and the requested profile is plausible.
65
+ 8. Handles `--dry-run` as a true no-write preview.
60
66
 
61
67
  Only after those checks does dispatch create run artifacts/worktrees, write the handoff/comment, move the ticket, or spawn Hermes.
62
68
 
@@ -67,14 +73,65 @@ In normal spawn mode:
67
73
  1. Builds a run id such as `orbit-12-agent-abc123def0`.
68
74
  2. Creates `.orbit/dispatch-runs/<run-id>/` for run metadata and policy wrappers.
69
75
  3. Creates and preserves a git worktree/branch when `--worktree` is used.
70
- 4. Generates a full agent handoff and writes the canonical copy to the ticket's **AI Written-Plan** (`ai_plan`) field.
71
- 5. Moves the ticket to **In Progress**.
72
- 6. Starts Hermes.
73
- 7. Comments a run record back onto the ticket.
76
+ 4. Generates a full agent handoff.
77
+ 5. Writes an initial durable `run-record.json` with status `prepared` before ticket mutation or agent spawn.
78
+ 6. Writes the canonical handoff copy to the ticket's **AI Written-Plan** (`ai_plan`) field.
79
+ 7. Moves the ticket to **In Progress**.
80
+ 8. Starts Hermes.
81
+ 9. Updates `run-record.json` to status `launched` with the child process id.
82
+ 10. Comments a run record back onto the ticket.
83
+
84
+ In `--no-spawn` mode, dispatch writes the handoff, initial `run-record.json`, and run-record comment but leaves the ticket in its current lane.
85
+
86
+ The run record includes profile, policy, ticket, branch, worktree, base commit, policy-bin path, child process id, command, declared verification commands, mode, and a `run-record.json` path. This makes the card the visible cockpit for the run while preserving a machine-readable artifact for later review/repair automation.
87
+
88
+ ## Structured run record
89
+
90
+ Every non-dry-run dispatch writes `<DATA_DIR>/dispatch-runs/<board>/<run-id>/run-record.json` with schema `orbit.dispatch.run.v1`.
91
+
92
+ The JSON record currently captures:
93
+
94
+ - run id, board id/slug, ticket id/number, profile, policy, mode, and status (`prepared` for `--no-spawn`, `launched` for spawned runs)
95
+ - prompt/handoff path, repo root, worktree, branch, and base commit
96
+ - process id when a Hermes child was spawned
97
+ - declared verification commands
98
+ - empty `commit_shas` and `residual_risks` arrays for completion/review tooling to fill in later slices
99
+ - a small environment-variance snapshot: platform, Node version, cwd, path format, and relevant `HOME`, `PROJECT_ROOT`, `DATA_DIR`, and `ORBIT_*` env vars
100
+
101
+ URL-bearing values are presence-only redacted in `run-record.json`. `ORBIT_API_URL`, `ORBIT_SERVER_URL`, and the top-level `server_url` field are recorded as `[redacted-url-present]` when present, rather than persisting usernames, passwords, query tokens, fragments, hostnames, or paths. Non-URL environment checklist values such as `HOME`, `PROJECT_ROOT`, `DATA_DIR`, `ORBIT_MODE`, and `ORBIT_DEFAULT_BOARD` remain visible for variance debugging.
102
+
103
+ Operators can override the default verification contract by repeating `--verify-command <cmd>` or using `--verify-command=<cmd>`. Bare `--verify-command`, `--verify-command=` and `--verify-command` followed by another flag are rejected before dispatch side effects. If omitted, dispatch declares the standard local-safe checklist: `npm test`, conditional `npm run build`, `node src/cli/orbit.js dispatch --help`, `git diff --check`, and `git diff --cached --check before commit`.
104
+
105
+ ## Structured review verdict records
74
106
 
75
- In `--no-spawn` mode, dispatch writes the handoff and run-record comment but leaves the ticket in its current lane.
107
+ Sentinel/agent review results are first-class board data in `review_verdicts`, not prose-only comments. Each record belongs to one ticket and stores:
76
108
 
77
- The run record includes profile, policy, ticket, branch, worktree, policy-bin path, child process id, command, and mode. This makes the card the visible cockpit for the run.
109
+ - `verdict`: one of `PASS`, `BLOCK`, or `QUESTION`
110
+ - `blocking_findings`: JSON array for issues that must be repaired before approval
111
+ - `optional_findings`: JSON array for non-blocking review notes
112
+ - `evidence_commands`: JSON array of commands or probes the reviewer used as evidence
113
+ - `reviewer_profile` and `reviewer_session_id`: optional reviewer identity/session metadata
114
+ - `reviewed_commit_sha`: optional commit SHA under review
115
+ - `dispatch_run_id`: optional dispatch run id tying the verdict back to `run-record.json`
116
+ - `supersedes_prior_review_id`: optional link to an earlier verdict on the same ticket
117
+
118
+ HTTP API:
119
+
120
+ ```text
121
+ POST /api/tickets/:ticket_id/review-verdicts
122
+ GET /api/tickets/:ticket_id/review-verdicts?limit=50
123
+ GET /api/review-verdicts/:review_id?board=<slug-or-id>
124
+ ```
125
+
126
+ MCP tools:
127
+
128
+ ```text
129
+ board_create_review_verdict
130
+ board_list_review_verdicts
131
+ board_get_review_verdict
132
+ ```
133
+
134
+ Comments may mirror verdict summaries for human scanning, but comments are not the source of truth for review automation. Later repair and re-review tooling should read these records directly.
78
135
 
79
136
  ## The AI Written-Plan handoff
80
137
 
@@ -91,10 +148,13 @@ Dispatch stores the generated handoff in the ticket's AI Written-Plan field. It
91
148
  - unresolved blockers
92
149
  - scope boundaries
93
150
  - autonomous policy
151
+ - declared verification commands and environment-variance checklist
94
152
  - completion protocol
95
153
 
96
154
  The short prompt passed to `hermes chat -q` is intentionally small. The ticket is the source of truth; the CLI argument is only a bootstrap pointer.
97
155
 
156
+ Board journal entries are framed as project constraints and lessons, not persona or roleplay instructions. Keep durable board entries focused on mechanisms, architectural/product boundaries, workflow invariants, public-product implications, and reusable pitfalls; use ticket fields/comments for run-specific chronology.
157
+
98
158
  ## Preserved worktrees
99
159
 
100
160
  Use `--worktree` when the agent will edit code. Dispatch creates a normal git worktree under `.worktrees/` by default and gives the agent that directory as its working directory.
File without changes
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starlove/orbit",
3
- "version": "1.1.2",
3
+ "version": "1.1.11",
4
4
  "description": "Starscape Orbit ??? zero-dependency, pretty, human-ai-first kanban system.",
5
5
  "license": "AGPL-3.0",
6
6
  "type": "module",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/public/index.html CHANGED
@@ -15,9 +15,129 @@
15
15
  sizes="180x180"
16
16
  href="./apple-touch-icon.png"
17
17
  />
18
+ <!-- Apply the stored theme + motion preference before first paint, so
19
+ the app underneath the splash paints in the right theme (no flash)
20
+ and the splash respects an explicit reduce-motion choice. Mirrors
21
+ theme-preference.js (mab_theme) and motion-preference.js
22
+ (mab_reduce_motion → data-motion); main.js reconciles on load. -->
23
+ <script>
24
+ try {
25
+ var t = localStorage.getItem("mab_theme");
26
+ if (t === "light" || t === "dark")
27
+ document.documentElement.setAttribute("data-theme", t);
28
+ var rm = localStorage.getItem("mab_reduce_motion");
29
+ if (rm === "true")
30
+ document.documentElement.setAttribute("data-motion", "reduce");
31
+ else if (rm === "false")
32
+ document.documentElement.setAttribute("data-motion", "full");
33
+ } catch (e) {}
34
+ </script>
35
+ <link rel="preload" as="image" href="./orbits-logo.png" />
18
36
  <link rel="stylesheet" href="/styles.css" />
37
+ <!-- Boot splash — the canonical ss-ui loading screen (see the package's
38
+ components/loader.css). Inline critical CSS so it paints before
39
+ /js/main.js loads. The logo grows in then breathes; no spinner.
40
+ Keyframes namespaced `ss-loader-*`. Under reduced motion the grow-in
41
+ is dropped and the dot grid stops; the gentle breathe stays. -->
42
+ <style>
43
+ .ss-loader {
44
+ position: fixed;
45
+ inset: 0;
46
+ z-index: 99999;
47
+ display: flex;
48
+ flex-direction: column;
49
+ align-items: center;
50
+ justify-content: center;
51
+ gap: 2rem;
52
+ overflow: hidden;
53
+ background: linear-gradient(
54
+ 135deg,
55
+ #1a1a2e 0%,
56
+ #16213e 50%,
57
+ #0f3460 100%
58
+ );
59
+ transition: opacity 0.4s ease;
60
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
61
+ Roboto, "Helvetica Neue", Arial, sans-serif;
62
+ }
63
+ .ss-loader::before {
64
+ content: "";
65
+ position: absolute;
66
+ width: 200%;
67
+ height: 200%;
68
+ background-image: radial-gradient(
69
+ circle,
70
+ rgba(168, 192, 255, 0.15) 1px,
71
+ transparent 1px
72
+ );
73
+ background-size: 50px 50px;
74
+ will-change: transform;
75
+ animation: ss-loader-drift 20s linear infinite;
76
+ }
77
+ .ss-loader.is-hiding {
78
+ opacity: 0;
79
+ pointer-events: none;
80
+ }
81
+ .ss-loader__logo {
82
+ width: 120px;
83
+ height: 120px;
84
+ object-fit: contain;
85
+ filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.3));
86
+ transform-origin: center;
87
+ will-change: transform;
88
+ animation:
89
+ ss-loader-grow 0.6s cubic-bezier(0.22, 1, 0.36, 1) both,
90
+ ss-loader-breathe 3s ease-in-out 0.6s infinite;
91
+ }
92
+ .ss-loader__label {
93
+ margin: 0;
94
+ color: #fff;
95
+ font-size: 1.25rem;
96
+ font-weight: 600;
97
+ letter-spacing: 0.5px;
98
+ text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
99
+ }
100
+ @keyframes ss-loader-drift {
101
+ from { transform: translate(0, 0); }
102
+ to { transform: translate(-50px, -50px); }
103
+ }
104
+ @keyframes ss-loader-grow {
105
+ from { transform: scale(0); opacity: 0; }
106
+ to { transform: scale(1); opacity: 1; }
107
+ }
108
+ @keyframes ss-loader-breathe {
109
+ 0%, 100% { transform: scale(1); }
110
+ 50% { transform: scale(1.06); }
111
+ }
112
+ @media (prefers-reduced-motion: reduce) {
113
+ :root:not([data-motion="full"]) .ss-loader::before {
114
+ animation: none;
115
+ }
116
+ :root:not([data-motion="full"]) .ss-loader__logo {
117
+ animation: ss-loader-breathe 3s ease-in-out infinite;
118
+ }
119
+ }
120
+ [data-motion="reduce"] .ss-loader::before {
121
+ animation: none;
122
+ }
123
+ [data-motion="reduce"] .ss-loader__logo {
124
+ animation: ss-loader-breathe 3s ease-in-out infinite;
125
+ }
126
+ </style>
19
127
  </head>
20
128
  <body>
129
+ <!-- Boot splash - shown immediately, removed once the app is ready. -->
130
+ <div id="ss-loader" class="ss-loader" role="status" aria-live="polite">
131
+ <img
132
+ class="ss-loader__logo"
133
+ src="./orbits-logo.png"
134
+ alt="Orbit"
135
+ width="120"
136
+ height="120"
137
+ />
138
+ <p class="ss-loader__label">Loading Orbit…</p>
139
+ </div>
140
+
21
141
  <div class="grain">
22
142
  <div class="milky-way" aria-hidden="true"></div>
23
143
  </div>
@@ -170,19 +290,10 @@
170
290
  ></div>
171
291
  <aside id="drawer" class="drawer" hidden aria-label="Panel">
172
292
  <header class="drawer-header">
173
- <div class="drawer-title-block">
174
- <span
175
- id="drawerEyebrow"
176
- class="drawer-eyebrow"
177
- hidden
178
- ></span>
179
- <h2 id="drawerTitle" class="drawer-title-text"></h2>
180
- <div
181
- id="drawerSubtitle"
182
- class="drawer-subtitle"
183
- hidden
184
- ></div>
185
- </div>
293
+ <div
294
+ id="drawerTitleBlock"
295
+ class="drawer-title-block"
296
+ ></div>
186
297
  <nav
187
298
  id="drawerTabs"
188
299
  class="drawer-tabs"
@@ -310,5 +421,39 @@
310
421
  </dialog>
311
422
 
312
423
  <script src="/js/main.js" type="module"></script>
424
+ <!-- Fade out the boot splash once the DOM is ready. The /js/main.js
425
+ module runs before DOMContentLoaded, so theme/motion + wiring are
426
+ already applied by the time this fires. -->
427
+ <script>
428
+ (function () {
429
+ function hideSplash() {
430
+ var splash = document.getElementById("ss-loader");
431
+ if (!splash) return;
432
+ // Keep the splash up for a minimum of 786ms (measured from
433
+ // navigation start) so a fast load doesn't flash it in and out.
434
+ var wait = Math.max(0, 786 - performance.now());
435
+ setTimeout(function () {
436
+ splash.classList.add("is-hiding");
437
+ splash.addEventListener(
438
+ "transitionend",
439
+ function () {
440
+ splash.remove();
441
+ },
442
+ { once: true }
443
+ );
444
+ setTimeout(function () {
445
+ splash.remove();
446
+ }, 600);
447
+ }, wait);
448
+ }
449
+ if (document.readyState === "loading") {
450
+ document.addEventListener("DOMContentLoaded", hideSplash, {
451
+ once: true
452
+ });
453
+ } else {
454
+ hideSplash();
455
+ }
456
+ })();
457
+ </script>
313
458
  </body>
314
459
  </html>
File without changes
package/public/js/api.js CHANGED
File without changes
package/public/js/app.js CHANGED
File without changes
File without changes
@@ -245,6 +245,25 @@ export function bindCardContextMenu(card, ticket, { handlers = createCardActionH
245
245
  });
246
246
  }
247
247
 
248
+ // Delegated variant: a single listener on the board container resolves the card
249
+ // and its ticket per event. Lets renderBoard() swap innerHTML freely without
250
+ // re-binding a contextmenu listener onto every card on every render.
251
+ export function bindCardContextMenuDelegated(container, resolveTicket, { handlers = createCardActionHandlers(), onBeforeOpen = null } = {}) {
252
+ container.addEventListener("contextmenu", (event) => {
253
+ const card = event.target instanceof Element ? event.target.closest(".card") : null;
254
+ if (!card) return;
255
+ const ticket = resolveTicket(card.dataset.ticketId);
256
+ if (!ticket) return;
257
+ event.preventDefault();
258
+ event.stopPropagation();
259
+ if (dragInFlight) {
260
+ closeCardActionMenu();
261
+ return;
262
+ }
263
+ openCardActionMenu({ ticket, anchor: card, x: event.clientX, y: event.clientY, handlers, onBeforeOpen });
264
+ });
265
+ }
266
+
248
267
  function renderMenuItems(items) {
249
268
  return `<ul class="card-action-menu-list" role="none">${items.map(renderMenuItem).join("")}</ul>`;
250
269
  }
File without changes
File without changes
package/public/js/dom.js CHANGED
File without changes