@bongos/core 1.21.40 → 1.21.42

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 (80) hide show
  1. package/.bongos-core.json +136 -76
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +16 -0
  4. package/clients/bongos-client/index.cjs +16 -0
  5. package/clients/bongos-client/index.d.ts +25 -0
  6. package/clients/bongos-client/index.mjs +16 -0
  7. package/docs/api/openapi.json +492 -3
  8. package/docs/api-reference.md +13 -1
  9. package/docs/copy-inventory.md +29 -12
  10. package/docs/copy-registry.json +177 -14
  11. package/docs/file-map.md +1 -0
  12. package/docs/module-api-changelog.md +4 -0
  13. package/docs/page-inventory.json +4 -2
  14. package/docs/page-readings.json +574 -547
  15. package/modules/autonomy/governor-docket.js +6 -1
  16. package/modules/hall-ui/public/atlas.html +3 -3
  17. package/modules/hall-ui/public/blockers.html +3 -3
  18. package/modules/hall-ui/public/board-room.html +3 -3
  19. package/modules/hall-ui/public/city.html +3 -3
  20. package/modules/hall-ui/public/collab.html +3 -3
  21. package/modules/hall-ui/public/commands.html +3 -3
  22. package/modules/hall-ui/public/copy-desk.html +3 -3
  23. package/modules/hall-ui/public/craft-mode.js +62 -0
  24. package/modules/hall-ui/public/deploy.html +3 -3
  25. package/modules/hall-ui/public/diagrams.html +3 -3
  26. package/modules/hall-ui/public/drachmae.html +3 -3
  27. package/modules/hall-ui/public/fleet.html +3 -3
  28. package/modules/hall-ui/public/gate.html +3 -3
  29. package/modules/hall-ui/public/goal-map.html +3 -3
  30. package/modules/hall-ui/public/goals.html +3 -3
  31. package/modules/hall-ui/public/government.html +3 -3
  32. package/modules/hall-ui/public/idea.html +3 -3
  33. package/modules/hall-ui/public/ideas.html +3 -3
  34. package/modules/hall-ui/public/index.html +3 -3
  35. package/modules/hall-ui/public/modules.html +3 -3
  36. package/modules/hall-ui/public/page-notes.js +320 -0
  37. package/modules/hall-ui/public/primer.html +3 -3
  38. package/modules/hall-ui/public/profile.html +3 -3
  39. package/modules/hall-ui/public/project-settings.html +3 -3
  40. package/modules/hall-ui/public/ranks.html +3 -3
  41. package/modules/hall-ui/public/roadmap.html +3 -3
  42. package/modules/hall-ui/public/roster.html +3 -3
  43. package/modules/hall-ui/public/sessions.html +3 -3
  44. package/modules/hall-ui/public/settings.html +3 -3
  45. package/modules/hall-ui/public/street-card.css +13 -0
  46. package/modules/hall-ui/public/street-card.js +61 -9
  47. package/modules/hall-ui/public/street-card.states.json +72 -0
  48. package/modules/hall-ui/public/street.html +3 -3
  49. package/modules/hall-ui/public/studio.html +3 -3
  50. package/modules/hall-ui/public/style.css +144 -0
  51. package/modules/hall-ui/public/task.html +3 -3
  52. package/modules/hall-ui/public/thinking.html +3 -3
  53. package/modules/hall-ui/public/tweak-editor.html +3 -3
  54. package/modules/hall-ui/public/watch.html +3 -3
  55. package/modules/hall-ui/public/work.html +3 -3
  56. package/modules/hall-ui/records/governor-street.md +1 -0
  57. package/modules/hall-ui/records/page-notes.md +16 -0
  58. package/modules/page-notes/CLAUDE.md +15 -0
  59. package/modules/page-notes/db.js +147 -0
  60. package/modules/page-notes/migrations/page_notes_001_notes.sql +68 -0
  61. package/modules/page-notes/module.json +13 -0
  62. package/modules/page-notes/notes.js +174 -0
  63. package/modules/page-notes/routes/page-notes.js +172 -0
  64. package/modules/page-notes/split.js +68 -0
  65. package/modules/provisioning/starter-bundles.js +1 -0
  66. package/modules/ui-design/kit/serve.js +1 -1
  67. package/package-lock.json +2 -2
  68. package/package.json +1 -1
  69. package/release-notes.json +12 -0
  70. package/scripts/gds/publish-manifest.js +1 -0
  71. package/src/bongos/module-scope-map.js +4 -0
  72. package/src/bongos/routes.js +4 -1
  73. package/src/module-api.js +1 -1
  74. package/tests/governor_docket.mjs +3 -1
  75. package/tests/hall_matter_card.mjs +71 -0
  76. package/tests/hall_tweak_editor.mjs +1 -1
  77. package/tests/module_loader.mjs +1 -1
  78. package/tests/page_notes.mjs +289 -0
  79. package/tests/page_notes_db.mjs +140 -0
  80. package/tests/page_notes_panel.mjs +145 -0
@@ -649,6 +649,78 @@
649
649
  "#mcardMore"
650
650
  ]
651
651
  }
652
+ },
653
+ "automation": {
654
+ "auth": true,
655
+ "_": "the switch is off with a long reason (task 1004568): the kicker is building · floor only, the reason is one cut line, and the card is only as tall as its words.",
656
+ "actions": [
657
+ [
658
+ "wait",
659
+ 900
660
+ ],
661
+ [
662
+ "clickIfVisible",
663
+ "[data-craft-close]"
664
+ ],
665
+ [
666
+ "wait",
667
+ 300
668
+ ],
669
+ [
670
+ "dispatchClick",
671
+ ".street-pin[data-matter=\"autobongos_fence:fence\"]"
672
+ ],
673
+ [
674
+ "wait",
675
+ 600
676
+ ]
677
+ ],
678
+ "expect": {
679
+ "visible": [
680
+ "#streetCard",
681
+ ".mcard-fact",
682
+ "[data-act=\"switch_on\"]"
683
+ ]
684
+ }
685
+ },
686
+ "automation-more": {
687
+ "auth": true,
688
+ "_": "the same card with more open: the switch reason in full (task 1004568).",
689
+ "actions": [
690
+ [
691
+ "wait",
692
+ 900
693
+ ],
694
+ [
695
+ "clickIfVisible",
696
+ "[data-craft-close]"
697
+ ],
698
+ [
699
+ "wait",
700
+ 300
701
+ ],
702
+ [
703
+ "dispatchClick",
704
+ ".street-pin[data-matter=\"autobongos_fence:fence\"]"
705
+ ],
706
+ [
707
+ "wait",
708
+ 600
709
+ ],
710
+ [
711
+ "click",
712
+ "[data-act=\"more\"]"
713
+ ],
714
+ [
715
+ "wait",
716
+ 300
717
+ ]
718
+ ],
719
+ "expect": {
720
+ "visible": [
721
+ "#mcardMore"
722
+ ]
723
+ }
652
724
  }
653
725
  },
654
726
  "reducedMotion": {
@@ -203,9 +203,9 @@
203
203
  </div>
204
204
  </div>
205
205
 
206
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
207
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
208
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
206
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
207
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
208
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
209
209
  <script src="/builders/palette.js" defer></script>
210
210
  <script src="/builders/city-draw.js?v=2026-10-02-street1"></script>
211
211
  <script src="/builders/street-draw.js?v=2026-10-02-street1"></script>
@@ -176,9 +176,9 @@
176
176
 
177
177
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
178
178
 
179
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
180
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
181
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
179
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
180
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
181
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
182
182
  <script src="/builders/palette.js" defer></script>
183
183
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
184
184
  <!-- The kit, for its ONE renderer: the full-size ship-time visual figure
@@ -1550,6 +1550,150 @@ html[data-theme="light"][data-craft="ideating"] .app-sidebar {
1550
1550
  radial-gradient(1.5px 1.5px at 170px 260px, rgba(184, 137, 28, .25), transparent);
1551
1551
  }
1552
1552
 
1553
+ /* THE MODE TOOLS (task 1004496, craft-mode.js TOOLS): a round button beside the
1554
+ switch, in the mode's colour, shown only in its own mode. Artistry's palette
1555
+ opens the page feedback doc (page-notes.js). */
1556
+ .craft-tool {
1557
+ display: inline-flex;
1558
+ align-items: center;
1559
+ justify-content: center;
1560
+ width: 32px; height: 32px;
1561
+ padding: 0;
1562
+ border: 0;
1563
+ border-radius: var(--radius-pill);
1564
+ background: none;
1565
+ color: var(--craft, var(--ink-soft));
1566
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--craft, var(--ink)) 40%, transparent);
1567
+ cursor: pointer;
1568
+ flex-shrink: 0;
1569
+ }
1570
+ .craft-tool:hover { background: color-mix(in srgb, var(--craft, var(--ink)) 12%, transparent); }
1571
+ .craft-tool:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
1572
+ .craft-tool[aria-disabled="true"] { opacity: .45; cursor: default; }
1573
+ .craft-tool__icon { width: 16px; height: 16px; flex-shrink: 0; }
1574
+ /* With room, the tool says what it does, so it cannot be read as the mode's own
1575
+ pill (same palette glyph); under 1180px it folds to the icon, as the switch does. */
1576
+ .craft-tool__label { font-size: 13px; white-space: nowrap; }
1577
+ @media (min-width: 1181px) {
1578
+ .craft-tool { width: auto; gap: 6px; padding: 0 12px 0 10px; }
1579
+ }
1580
+ @media (max-width: 1180px) { .craft-tool__label { display: none; } }
1581
+
1582
+ /* The page feedback doc: a sheet from the right over a scrim. */
1583
+ html.pn-open { overflow: hidden; }
1584
+ .pn {
1585
+ position: fixed;
1586
+ inset: 0;
1587
+ z-index: var(--z-modal);
1588
+ display: flex;
1589
+ justify-content: flex-end;
1590
+ background: var(--scrim);
1591
+ }
1592
+ .pn__panel {
1593
+ position: relative;
1594
+ width: min(560px, 100%);
1595
+ height: 100%;
1596
+ box-sizing: border-box;
1597
+ display: flex;
1598
+ flex-direction: column;
1599
+ background: var(--bg-card);
1600
+ color: var(--ink);
1601
+ box-shadow: 0 0 0 1px var(--glass-edge), var(--shadow-pop);
1602
+ }
1603
+ .pn__head { position: relative; padding: 22px 64px 14px 24px; border-bottom: 1px solid var(--rule-soft); }
1604
+ .pn__eyebrow {
1605
+ margin: 0 0 6px;
1606
+ font-family: var(--label-face);
1607
+ font-size: 11px;
1608
+ letter-spacing: .12em;
1609
+ text-transform: uppercase;
1610
+ color: var(--craft, var(--ink-faint));
1611
+ }
1612
+ .pn__title { margin: 0; font-family: var(--font-display); font-weight: 400; font-size: 22px; letter-spacing: -0.02em; }
1613
+ .pn__close {
1614
+ position: absolute; top: 14px; right: 14px;
1615
+ display: inline-flex; align-items: center; justify-content: center;
1616
+ width: 40px; height: 40px; padding: 0; border: 0; border-radius: var(--radius-pill);
1617
+ background: none; color: var(--ink-soft); cursor: pointer;
1618
+ }
1619
+ .pn__close svg { width: 18px; height: 18px; }
1620
+ .pn__close:hover { color: var(--ink); background: var(--glass); }
1621
+ .pn__body { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 24px 28px; display: flex; flex-direction: column; gap: 12px; }
1622
+ .pn__label { font-size: 13px; color: var(--ink-soft); }
1623
+ .pn__text {
1624
+ width: 100%;
1625
+ min-height: 260px;
1626
+ box-sizing: border-box;
1627
+ padding: 14px 16px;
1628
+ border: 1px solid var(--rule);
1629
+ border-radius: var(--radius-sm);
1630
+ background: var(--bg);
1631
+ color: var(--ink);
1632
+ font: inherit;
1633
+ font-size: 15px;
1634
+ line-height: 1.55;
1635
+ resize: vertical;
1636
+ }
1637
+ .pn__text:focus { outline: 2px solid var(--focus); outline-offset: 1px; }
1638
+ .pn__tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
1639
+ .pn__add {
1640
+ position: relative;
1641
+ display: inline-flex; align-items: center;
1642
+ min-height: 34px; padding: 0 14px;
1643
+ border-radius: var(--radius-pill);
1644
+ box-shadow: inset 0 0 0 1px var(--rule);
1645
+ font-size: 13.5px; color: var(--ink); cursor: pointer;
1646
+ }
1647
+ .pn__add:hover { background: var(--glass); }
1648
+ .pn__add:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }
1649
+ .pn__add input { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; }
1650
+ .pn__quiet { margin: 0; font-size: 13px; color: var(--ink-faint); }
1651
+ .pn__saved { margin-left: auto; }
1652
+ .pn__thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 8px; }
1653
+ .pn__thumb { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 8px; background: var(--bg-deep); }
1654
+ .pn__error { margin: 0; font-size: 13.5px; color: var(--danger); }
1655
+ .pn__error:empty { display: none; }
1656
+ .pn__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; padding-top: 4px; }
1657
+ .pn__hint { margin: 0; font-size: 13px; line-height: 1.45; color: var(--ink-soft); }
1658
+ .pn__foot .pn__hint { flex: 1 1 240px; }
1659
+ .pn__submit {
1660
+ min-height: 40px; padding: 0 20px; border: 0; border-radius: var(--radius-pill);
1661
+ background: var(--accent); color: var(--accent-on); font: inherit; font-weight: 500; cursor: pointer;
1662
+ }
1663
+ .pn__submit:hover { background: var(--accent-lift); }
1664
+ .pn__submit:disabled { opacity: .55; cursor: default; }
1665
+ .pn__submit:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
1666
+ .pn__list { margin-top: 14px; padding-top: 16px; border-top: 1px solid var(--rule-soft); }
1667
+ .pn__list-h {
1668
+ margin: 0 0 8px;
1669
+ font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space);
1670
+ text-transform: var(--label-case); color: var(--ink-faint); font-weight: 400;
1671
+ }
1672
+ .pn__rows, .pn__ideas { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
1673
+ .pn__row {
1674
+ display: grid; gap: 2px; width: 100%; padding: 10px 12px; border: 0; border-radius: var(--radius-sm);
1675
+ background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer;
1676
+ }
1677
+ .pn__row:hover { background: var(--glass); }
1678
+ .pn__row:focus-visible, .pn__back:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
1679
+ .pn__row-meta { font-size: 12px; color: var(--ink-faint); }
1680
+ .pn__row-text { font-size: 14px; color: var(--ink-soft); }
1681
+ .pn__ideas a { display: inline-block; padding: 6px 0; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
1682
+ .pn__back {
1683
+ align-self: flex-start; min-height: 32px; padding: 0 12px; margin-left: -12px; border: 0;
1684
+ border-radius: var(--radius-pill); background: none; color: var(--ink-soft); font: inherit; font-size: 13.5px; cursor: pointer;
1685
+ }
1686
+ .pn__back:hover { color: var(--ink); background: var(--glass); }
1687
+ .pn__done { margin: 0; font-size: 16px; }
1688
+ .pn-doc { font-size: 15px; line-height: 1.6; overflow-wrap: anywhere; }
1689
+ .pn-doc p, .pn-doc ul { margin: 0 0 12px; }
1690
+ .pn-doc__h { margin: 16px 0 6px; font-size: 16px; font-weight: 500; }
1691
+ .pn-doc__img { display: block; max-width: 100%; height: auto; margin: 8px 0; border-radius: 8px; }
1692
+ @media (max-width: 640px) {
1693
+ .pn__head { padding: 18px 60px 12px 16px; }
1694
+ .pn__body { padding: 14px 16px 24px; }
1695
+ }
1696
+
1553
1697
  /* THE BACK LINK (task 1004430): at the head of the bar when the page was
1554
1698
  reached from inside another hall page. 24px floor on both axes. */
1555
1699
  .topbar-back {
@@ -54,9 +54,9 @@
54
54
 
55
55
  <!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
56
56
  time so the chrome exists before the deferred page scripts execute. -->
57
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
58
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
59
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
57
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
58
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
59
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
60
60
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
61
61
  <script src="/builders/palette.js" defer></script>
62
62
  <!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
@@ -138,9 +138,9 @@
138
138
  </div>
139
139
  </div>
140
140
 
141
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
142
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
143
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
141
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
142
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
143
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
144
144
  <script src="/builders/palette.js" defer></script>
145
145
 
146
146
  <script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
@@ -142,9 +142,9 @@
142
142
 
143
143
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
144
144
 
145
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
146
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
147
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
145
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
146
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
147
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
148
148
  <script src="/builders/palette.js" defer></script>
149
149
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
150
150
  <script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
@@ -162,9 +162,9 @@
162
162
 
163
163
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
164
164
  chrome exists before the page script runs. -->
165
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
166
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
167
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
165
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
166
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
167
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
168
168
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
169
169
  <script src="/builders/palette.js" defer></script>
170
170
  <!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
@@ -171,9 +171,9 @@
171
171
 
172
172
  <!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
173
173
  time so the chrome exists before the deferred page scripts execute. -->
174
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
175
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
176
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
174
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
175
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
176
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
177
177
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
178
178
  <script src="/builders/palette.js" defer></script>
179
179
  <!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
@@ -10,6 +10,7 @@ What it settled:
10
10
  - **The artboard is the street band.** The prototype's 1440 x 900 board less its 64 px header and 128 px footer, which the page lays out as real rows; `street.js` scales the 1440 x 708 band to the window, and on a phone keeps it near full size and lets the street scroll sideways inside its own window, so a pin stays a pin. The header fits the 80 px top-bar floor at 390 and 320 by going to two rows, its three figures as their own sideways strip.
11
11
  - **Real data where the prototype had samples.** One district per open goal (up to eight plots; past that seven stand and the eighth is a staked lot that opens the goals page), each goal's tasks drawn in proportion (built, scaffold, the prototype's `ready` slab), Review held while any source that stands in it can be read, its open matters as crates and the dashed "waiting for a tester" count pin, a reading off drawn as a dash and its reason. The full list is the header of `street-draw.js`.
12
12
  - **Every building and district is a door** to `/street#building=<key>` or `/street#district=<goal id>`, where its interior opens in place of the street (below). A pin opens the matter's short card (task 1004528 makes it act); its link is offered only for an on-site path.
13
+ - **A card is only as tall as its words** (task 1004568). Every part of the compact card has a cap (`LINE` in `street-card.js`): the kicker, the fact and a listed line one line, the title about two, a voice's reason two, the proposer's words three. Longer text is cut at a whole word with an ellipsis, and whatever was cut is whole under "more". The kicker is building · floor only: the automation switch's off-reason is no longer joined into it, it is the card's fact. Nothing on the card has a min-height. Rendered through `street-card.states.json` (`automation`, `automation-more`, beside the blocker and Full Idea states) at 1440 and 390, light and dark, on the hall-preview harness.
13
14
 
14
15
  Tests: `tests/hall_street.mjs` holds the kit, the row, the pins, the doors and the crates equal to the prototype by running its own `Component` class beside `street-draw.js` in both modes, then the docket rules and the page itself in a vm. The states file is `public/street.states.json`; render it against the hall-preview harness, whose `government__docket.json` fixture carries the seven sample matters.
15
16
 
@@ -0,0 +1,16 @@
1
+ # Page feedback docs — the Artistry palette (task 1004496, goal 1000095 WA6)
2
+
3
+ Covers: the palette tool in the top bar (`craft-mode.js` `TOOLS`), the feedback sheet (`page-notes.js`, `.pn*` and `.craft-tool*` in `style.css`). The store and its rules are `modules/page-notes/CLAUDE.md`.
4
+
5
+ ## What it is
6
+
7
+ - In **Artistry** mode a round palette button sits right before the mode switch ("Feedback" beside it from 1181px up, the glyph alone below, as the switch folds). Pressing it opens a sheet from the right: "What should this page be?", a long text area, **Add images** (PNG, JPG, GIF, WebP, 4 MB each, eight per doc), and **Turn into ideas**. The draft saves as you type; closing loses nothing, and reopening on the same page resumes it.
8
+ - On submit the server's AI split files each point as its own idea under the artist's name; the sheet shows the ideas it became. Under the writing area, **This page's reference docs** lists the page's submitted docs; each opens in place.
9
+ - **`?note=N` on any hall page opens that doc on arrival, in any mode.** Every idea a doc became links there, which is how the doc is retrievable from its ideas.
10
+
11
+ ## The rules an edit here must keep
12
+
13
+ - **The tool's code is not on every page.** `craft-mode.js` loads `page-notes.js` the first time the palette is pressed (or a `?note=` link arrives), at the same `?v` as itself. A new mode tool (paintbrush 1004497, lightbulb 1004498) is a `TOOLS` entry with its own script, not more code in `craft-mode.js`.
14
+ - **The reader shows only this module's own images** (`/api/bongos/page-notes/images/<id>`); any other image markdown stays text, and the doc is escaped. `tests/page_notes_panel.mjs` pins it.
15
+ - **The page id is the page inventory's** — `builders:` plus `body[data-page]` — so a doc keys to the same id every other page record uses.
16
+ - **Colours stay in `style.css`.** The tool takes `--craft` (falling back to ink outside a looked mode); the submit button keeps the accent, because it is the thing to press.
@@ -0,0 +1,15 @@
1
+ # @bongos/core — installed platform dependency (not your project's CLAUDE.md)
2
+
3
+ This file is a placeholder shipped inside the **@bongos/core** package. The core's own
4
+ internal agent notes are intentionally omitted from the published artifact so they cannot
5
+ load into your instance's context.
6
+
7
+ **It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
8
+ directory, and the live Bongos database are the source of truth for how work happens here
9
+ and who may do what.
10
+
11
+ You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
12
+ Nothing in here needs to be read to use the platform — run `bongos help` for commands.
13
+
14
+ _To work ON the platform core itself, use the standalone @bongos/core source repository,
15
+ not this vendored copy._
@@ -0,0 +1,147 @@
1
+ // modules/page-notes/db.js — the thin SQL behind a page feedback doc (task
2
+ // 1004496). Every function takes an optional executor so a test can hand in a
3
+ // fake query function; the default is the shared pool.
4
+
5
+ 'use strict';
6
+
7
+ const { pool } = require('../../src/module-api');
8
+
9
+ const exec = (executor) => executor || ((text, params) => pool.query(text, params));
10
+
11
+ const COLUMNS = `n.id, n.page_id, n.page_path, n.page_title, n.body_md, n.author_id, n.status, n.split_by,
12
+ n.idea_ids, n.created_at, n.updated_at, n.submitted_at, n.filed_at,
13
+ b.github_login AS author_login, COALESCE(NULLIF(b.display_name, ''), b.github_login) AS author_name`;
14
+
15
+ // The builder's open draft for a page, made if there is none. ON CONFLICT on
16
+ // the one-draft index makes two tabs opening at once resolve to one row.
17
+ async function openDraft({ authorId, pageId, pagePath, pageTitle }, executor) {
18
+ const q = exec(executor);
19
+ await q(
20
+ `INSERT INTO page_notes_docs (page_id, page_path, page_title, author_id)
21
+ VALUES ($1, $2, $3, $4)
22
+ ON CONFLICT (author_id, page_id) WHERE status = 'draft' DO NOTHING`,
23
+ [pageId, pagePath, pageTitle, authorId],
24
+ );
25
+ const r = await q(
26
+ `SELECT ${COLUMNS} FROM page_notes_docs n JOIN builders b ON b.id = n.author_id
27
+ WHERE n.author_id = $1 AND n.page_id = $2 AND n.status = 'draft'`,
28
+ [authorId, pageId],
29
+ );
30
+ return r.rows[0] || null;
31
+ }
32
+
33
+ async function getNote(id, executor) {
34
+ const q = exec(executor);
35
+ const r = await q(`SELECT ${COLUMNS} FROM page_notes_docs n JOIN builders b ON b.id = n.author_id WHERE n.id = $1`, [id]);
36
+ return r.rows[0] || null;
37
+ }
38
+
39
+ // A page's submitted docs, newest first, with no body (the list shows an excerpt).
40
+ async function listForPage(pageId, { limit = 20 } = {}, executor) {
41
+ const q = exec(executor);
42
+ const r = await q(
43
+ `SELECT n.id, n.page_id, n.page_title, n.status, n.submitted_at, n.idea_ids,
44
+ left(n.body_md, 400) AS excerpt,
45
+ COALESCE(NULLIF(b.display_name, ''), b.github_login) AS author_name
46
+ FROM page_notes_docs n JOIN builders b ON b.id = n.author_id
47
+ WHERE n.page_id = $1 AND n.status <> 'draft'
48
+ ORDER BY n.submitted_at DESC NULLS LAST, n.id DESC
49
+ LIMIT $2`,
50
+ [pageId, limit],
51
+ );
52
+ return r.rows;
53
+ }
54
+
55
+ async function saveBody(id, authorId, bodyMd, executor) {
56
+ const q = exec(executor);
57
+ const r = await q(
58
+ `UPDATE page_notes_docs SET body_md = $3, updated_at = now()
59
+ WHERE id = $1 AND author_id = $2 AND status = 'draft'
60
+ RETURNING id, updated_at`,
61
+ [id, authorId, bodyMd],
62
+ );
63
+ return r.rows[0] || null;
64
+ }
65
+
66
+ async function countImages(noteId, executor) {
67
+ const q = exec(executor);
68
+ const r = await q('SELECT count(*)::int AS n FROM page_notes_images WHERE note_id = $1', [noteId]);
69
+ return r.rows[0] ? r.rows[0].n : 0;
70
+ }
71
+
72
+ async function addImage(noteId, { content_type: type, bytes, name }, executor) {
73
+ const q = exec(executor);
74
+ const r = await q(
75
+ `INSERT INTO page_notes_images (note_id, content_type, name, bytes)
76
+ VALUES ($1, $2, $3, $4) RETURNING id, name, content_type, octet_length(bytes) AS size`,
77
+ [noteId, type, name, bytes],
78
+ );
79
+ return r.rows[0] || null;
80
+ }
81
+
82
+ async function listImages(noteId, executor) {
83
+ const q = exec(executor);
84
+ const r = await q(
85
+ 'SELECT id, name, content_type, octet_length(bytes) AS size FROM page_notes_images WHERE note_id = $1 ORDER BY id',
86
+ [noteId],
87
+ );
88
+ return r.rows;
89
+ }
90
+
91
+ // One image with the status and author of the doc it belongs to, which is what
92
+ // decides who may see it.
93
+ async function getImage(id, executor) {
94
+ const q = exec(executor);
95
+ const r = await q(
96
+ `SELECT i.id, i.content_type, i.bytes, n.status, n.author_id
97
+ FROM page_notes_images i JOIN page_notes_docs n ON n.id = i.note_id WHERE i.id = $1`,
98
+ [id],
99
+ );
100
+ return r.rows[0] || null;
101
+ }
102
+
103
+ // Submit: the draft becomes 'splitting', and images its text no longer shows
104
+ // are dropped. Conditional on 'draft', so a double click submits once.
105
+ async function markSplitting(id, authorId, keepImageIds, executor) {
106
+ const q = exec(executor);
107
+ const r = await q(
108
+ `UPDATE page_notes_docs SET status = 'splitting', submitted_at = now(), updated_at = now()
109
+ WHERE id = $1 AND author_id = $2 AND status = 'draft' RETURNING id`,
110
+ [id, authorId],
111
+ );
112
+ if (!r.rows[0]) return false;
113
+ await q('DELETE FROM page_notes_images WHERE note_id = $1 AND NOT (id = ANY($2::bigint[]))', [id, keepImageIds]);
114
+ return true;
115
+ }
116
+
117
+ async function markFiled(id, { ideaIds, splitBy }, executor) {
118
+ const q = exec(executor);
119
+ const r = await q(
120
+ `UPDATE page_notes_docs SET status = 'filed', idea_ids = $2::bigint[], split_by = $3, filed_at = now(), updated_at = now()
121
+ WHERE id = $1 AND status = 'splitting' RETURNING id`,
122
+ [id, ideaIds, splitBy],
123
+ );
124
+ return !!r.rows[0];
125
+ }
126
+
127
+ // The ideas a doc became, by title, for the doc's own view.
128
+ async function ideaTitles(ids, executor) {
129
+ if (!ids || !ids.length) return [];
130
+ const q = exec(executor);
131
+ const r = await q('SELECT id, title, status FROM idea_inbox WHERE id = ANY($1::bigint[]) ORDER BY id', [ids]);
132
+ return r.rows;
133
+ }
134
+
135
+ module.exports = {
136
+ openDraft,
137
+ getNote,
138
+ listForPage,
139
+ saveBody,
140
+ countImages,
141
+ addImage,
142
+ listImages,
143
+ getImage,
144
+ markSplitting,
145
+ markFiled,
146
+ ideaTitles,
147
+ };
@@ -0,0 +1,68 @@
1
+ -- page_notes_001_notes.sql — an artist's feedback doc on one hall page, and the
2
+ -- reference images it carries (task 1004496, goal 1000095 WA6).
3
+ --
4
+ -- In Artistry mode the palette button opens a feedback doc for the page you are
5
+ -- on. The doc can be as long as it needs to be and carries uploaded reference
6
+ -- images. On submit an AI splits it into separate ideas in the idea inbox, each
7
+ -- attributed to the artist, and the doc itself STAYS as the page's design
8
+ -- reference: later tasks on that page can cite it, and every idea it became
9
+ -- links back to it.
10
+ --
11
+ -- WHY A ROW AND NOT THE COPY DESK. The copy desk's rule is "no live CMS": it may
12
+ -- hold no wording that could become product copy. A feedback doc is not product
13
+ -- copy — it is a person's judgement about a page, like an idea's body — so it
14
+ -- lives in its own module, and nothing here is ever rendered as the page's text.
15
+ --
16
+ -- page_id is the page inventory's stable id, `<surface>:<page>` (docs/page-
17
+ -- inventory.json). page_path is where the doc's links lead back to.
18
+ -- Images live in the database, not on disk: a doc holds a handful of
19
+ -- screenshots, and a row survives every core upgrade and backup with no path
20
+ -- to get wrong (the task-visuals store had to be moved once for exactly that).
21
+
22
+ CREATE TABLE IF NOT EXISTS page_notes_docs (
23
+ id BIGSERIAL PRIMARY KEY,
24
+ page_id TEXT NOT NULL,
25
+ page_path TEXT NOT NULL DEFAULT '',
26
+ page_title TEXT NOT NULL DEFAULT '',
27
+ body_md TEXT NOT NULL DEFAULT '',
28
+ author_id BIGINT NOT NULL REFERENCES builders(id),
29
+ status TEXT NOT NULL DEFAULT 'draft',
30
+ split_by TEXT,
31
+ idea_ids BIGINT[] NOT NULL DEFAULT '{}',
32
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
33
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
34
+ submitted_at TIMESTAMPTZ,
35
+ filed_at TIMESTAMPTZ,
36
+ CONSTRAINT page_notes_page_id_chk CHECK (page_id ~ '^[a-z][a-z0-9-]{0,31}:[a-z0-9][a-z0-9_-]{0,63}$'),
37
+ CONSTRAINT page_notes_page_path_chk CHECK (length(page_path) <= 200 AND (page_path = '' OR page_path ~ '^/[A-Za-z0-9/_.-]*$')),
38
+ CONSTRAINT page_notes_page_title_chk CHECK (length(page_title) <= 120),
39
+ CONSTRAINT page_notes_body_chk CHECK (length(body_md) <= 40000),
40
+ CONSTRAINT page_notes_status_chk CHECK (status IN ('draft', 'splitting', 'filed')),
41
+ CONSTRAINT page_notes_split_by_chk CHECK (split_by IS NULL OR split_by IN ('ai', 'whole')),
42
+ CONSTRAINT page_notes_ideas_bounded CHECK (cardinality(idea_ids) <= 20)
43
+ );
44
+
45
+ -- One open draft per builder per page: reopening the palette resumes it.
46
+ CREATE UNIQUE INDEX IF NOT EXISTS page_notes_one_draft
47
+ ON page_notes_docs (author_id, page_id) WHERE status = 'draft';
48
+ -- The page's reference docs, newest first.
49
+ CREATE INDEX IF NOT EXISTS page_notes_by_page
50
+ ON page_notes_docs (page_id, submitted_at DESC) WHERE status <> 'draft';
51
+
52
+ CREATE TABLE IF NOT EXISTS page_notes_images (
53
+ id BIGSERIAL PRIMARY KEY,
54
+ note_id BIGINT NOT NULL REFERENCES page_notes_docs(id) ON DELETE CASCADE,
55
+ content_type TEXT NOT NULL,
56
+ name TEXT NOT NULL DEFAULT '',
57
+ bytes BYTEA NOT NULL,
58
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
59
+ -- SVG is deliberately absent: it is an executable document, and these are
60
+ -- served from our own origin (the task-visuals rule).
61
+ CONSTRAINT page_notes_images_type_chk CHECK (content_type IN ('image/png', 'image/jpeg', 'image/gif', 'image/webp')),
62
+ CONSTRAINT page_notes_images_size_chk CHECK (octet_length(bytes) BETWEEN 1 AND 4194304),
63
+ CONSTRAINT page_notes_images_name_chk CHECK (length(name) <= 120)
64
+ );
65
+ CREATE INDEX IF NOT EXISTS page_notes_images_by_note ON page_notes_images (note_id);
66
+
67
+ INSERT INTO schema_migrations (version) VALUES ('page_notes_001_notes')
68
+ ON CONFLICT DO NOTHING;
@@ -0,0 +1,13 @@
1
+ {
2
+ "key": "page-notes",
3
+ "title": "Page feedback docs (the artist's palette)",
4
+ "description": "An artist's feedback doc on one hall page (task 1004496, goal 1000095 WA6). In Artistry mode the palette button opens a doc for the page you are on: as long as it needs to be, with uploaded reference images (PNG, JPG, GIF, WebP, up to 4 MB, eight per doc). On submit an AI splits it into separate ideas in the idea inbox, each attributed to the artist and linking back to the doc, and the doc stays as the page's design reference, readable from the page (?note=N) and from every idea it became. Owner decisions 2026-10-04: an AI splits it; images only. Kept apart from the copy desk on purpose: that module may hold no wording, and a feedback doc is wording about a page (not of it). default-on, like the copy desk: every instance has pages an artist can comment on.",
5
+ "version": "1.0.0",
6
+ "coreVersion": "^1.19.0",
7
+ "default": true,
8
+ "maintenance": { "status": "core-maintained" },
9
+ "migrations": true,
10
+ "contributes": { "routes": ["page-notes"] },
11
+ "provides": [],
12
+ "consumes": ["ideas.capture"]
13
+ }