@bongos/core 1.19.400 → 1.19.401

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.
@@ -106,3 +106,41 @@ The criterion `apex-hub-chrome-and-card` moves to the builds: task 1003300 (the
106
106
  - **Task 1003300** builds v2's bar, menus (Sky / Table / Your projects / New project) and search, with Only mine beside the search and the toolbar retired, into `public-landing/projects.html` + `cosmos.css` (the hub's sheet moves onto the world's derived tier the way `world.css` did, keeping `--line` and the status vocabulary), retargets the tests in §6, regenerates the copy registry.
107
107
  - **Task 1003301** builds the picked card and its ladder, adds the enrichment fields (§6) inside the same `Promise.allSettled` with the shared null object, gates the hall entry on the new hall-address field and membership, and keeps the orbs and the hover card untouched.
108
108
  - The unpicked versions stay on disk as built and verified; what they leave behind is listed per version in §3.
109
+
110
+ ## 9. The owner's review of the shipped chrome, and the table in the world (task 1003332)
111
+
112
+ **Date:** 2026-08-28 · three corrections from the owner's screenshot review of `/projects` (the Projects band open, the table view). Each overrides §7 where they conflict. Rendered through the kit before and after (`node modules/ui-design/kit/render.js --page /projects`, both modes, 1440 / 390 / 320; the landing the same) — ALL CLEAN throughout; the probes 26/26 on the hub and 11/11 on the landing.
113
+
114
+ ### 9.1 About and Docs leave the Projects band: the footer row
115
+
116
+ They are site pages, not project views. Two placements were rendered as a before/after on both pages (dark and light, 1440 and 390): **(A)** beside the identity slot as two lowercase text triggers (`about docs projects`), folding away under 900; **(B)** the footer row the landing already had (About · Docs · Privacy · Terms · Contact · Builders' hall). **Pick: B, the footer, on both pages.** One home at every width (A needs the footer anyway under 900 — two homes for two links); the bar stays the Operate bar given 3 asked for ("this way we dont clutter the bar"); About and Docs are the same kind of thing as Privacy / Terms / Contact. Consequences: the `bandMore` rung and its rules left both sheets; the hub's footer row wraps (`flex-wrap`) and `--foot-h` was re-measured with six links in it (136px under 420, 154px under 340, so the sky still fits the viewport); the About anchor stays the landing's lifecycle band (`/#about` from the hub, `#about` on the landing) until goal 1000063's page takes it. Probes retargeted: the band's last item is New project, the signed-out band is three cells, the footer carries both hrefs on both pages.
117
+
118
+ ### 9.2 The bands overlay; the page does not reflow
119
+
120
+ `.menuBand` is `position:absolute; left:0; right:0; top:var(--top-h); z-index:49` against the document in both sheets. Everything else the disclosure did stays: full width on `bg-card` closed by one hairline, `aria-expanded` + `aria-controls` and no menu role, Escape / arrows / Enter / Tab-out / click-outside, one open at a time, the 220ms rise + fade on the world's ease-out, dead under the kill switch. The stack: band 49 < bar 50 < the folded search row 51 < the card scrim 60. The kit gained a `top[[sel,px]]` expectation for exactly this proof; the probes assert `#hubMain` at 64px (60 at 390) and the landing's `#main` at 72px both before and after a band opens. DESIGN.md's Navigation paragraph now says overlay.
121
+
122
+ ### 9.3 Found on the way: the Table deep link landed on the sky
123
+
124
+ `setView()` rewrites the URL to `?view=map` at boot, and the shipped boot read `sub=table` *after* it — so the landing's Table item (and any shared `/projects?view=map&sub=table`) opened the sky. The boot query is now read once, before `setView`; a probe opens the deep link and expects the table on stage with Table current; `tests/projects_hub.mjs` pins the order.
125
+
126
+ ### 9.4 The table in the world: one grammar, three mastheads
127
+
128
+ **Shared by every treatment (built):** the panel is gone — no `--card` ground, no 20px radius, no `--shadow-card`. The rows are DESIGN.md's ledger rows: 1px `rule-soft` hairlines, 17px of vertical air, the header a `rule` hairline under mono labels in `--label-face`. Builders is set in the figure voice (Manrope 300, 22px, tabular); the project name at 17px/500; the relationship pill is a mono label on a hairline — it states a fact and is not pressable, so the accent left it (the One Warm Thing Rule, §5); the row's door is `open →`, lowercase `accent-deep` text with an underline (the footer-link grammar, 24px tall) in place of the ghost pill; a row's hover is its hairline taking ink, the band cells' grammar, not an accent tint; the status chip keeps its fixed vocabulary; the view title is the display voice (`every project, as rows`). The objects are the landing's alpha **cutouts in both modes** — the view wrap is a stacking context, so a black plate under `lighten` could not reach the page ground the way the hero's do, and a cutout needs no blend — with the world's one contact shadow in light and none in dark. The plate tokens (`--plate-*-cut`) joined cosmos's derived tier by root-absolute path; the mode mapping (`--mast-shadow`, `--mast-scrim`) is in both dark blocks.
129
+
130
+ Three mastheads, rendered dark and light at 1440 / 390 / 320 (ALL CLEAN) and measured:
131
+
132
+ | # | Masthead | Height 1440 / 390 | Honest risk |
133
+ |---|---|---|---|
134
+ | **T1 the ledger** | the title at the left, the set's small bead (the landing's `.o3` crop) at the right | 218 / 121 px | the least new information — a chrome object beside a word; also the quietest and the cheapest |
135
+ | **T2 the field** | the sweeping form (the landing's `.form` crop) crossing the masthead behind the title, its left edge masked, a black scrim under the title in dark | 300 / 150 px (+5px over the viewport at 1440) | the crop is cut flat by the wrap's right edge and the masthead's bottom: on black the bottom edge shows, on paper both read as a torn photograph — DESIGN.md's own warning about a cropped form; a pick needs a bottom mask and a right feather, and the title wraps to two lines at 14ch |
136
+ | **T3 the specimen** | the bongo pair (the hero object, whole) at the left, the title beside it, two leader-line callouts with the rows' own numbers (projects, builders) — the landing's hero grammar | 294 / 283 px (+360px of scroll at 390) | the most height on a phone; the figures are live code, and the builders figure summed the rows before the platform's own count had landed (13, not 26) — a pick wires it to refresh when `#selfBuilders` lands and pins it in `tests/projects_hub.mjs` |
137
+
138
+ **Recommendation: T1.** It carries the world's material into the view at the smallest cost, holds at 390 and 320 without an edge problem in either mode, and is the treatment the mine view and the wizard can repeat (a title and one object). The owner picked from the rendered screens, not from this table.
139
+
140
+ **The owner's pick (2026-08-28): T1, the ledger — with the title cut to two words.** "t1, but I dont want the ', as rows'": the view title is `every project`. The two unpicked mastheads, the `?tbl=` switch, the three render-round states and T3's figure writes left with the pick; `tests/projects_hub.mjs` pins the masthead, the ledger rules, the row's door and the absence of the switch.
141
+
142
+ ### 9.5 What carries forward
143
+
144
+ - Task 1003301 (the poster card): the ledger grammar above is the card's facts strip, too.
145
+ - Task 1003319 (the planets): the masthead object rides the same crop maths the orbs will take.
146
+ - The mine view and the wizard are the next views to take a masthead in the picked treatment.
@@ -69,7 +69,7 @@ Then a state's own `expect` (a band open, a dialog present, a field's value, a c
69
69
  }
70
70
  ```
71
71
 
72
- Actions: `click`, `focus`, `press`, `fill`, `wait`, `clickAt x y`, `clickIfVisible`, `dispatchClick`, `check`, `back`, `forward`. Expectations: `expanded`, `dialog`, `visible[]`, `hidden[]`, `open`, `closed`, `activeId`, `activeText`, `expandedIds[]`, `param{}`, `attr[[sel,name,value]]`, `text[[sel,value]]`, `href[[sel,value]]`, `count[[sel,n]]`, `value[[sel,v]]`, `hasClass[[sel,class,bool]]`, `checked`, `noOverflow`. The kit validates the file before it opens a browser and names every problem.
72
+ Actions: `click`, `focus`, `press`, `fill`, `wait`, `clickAt x y`, `clickIfVisible`, `dispatchClick`, `check`, `back`, `forward`. Expectations: `expanded`, `dialog`, `visible[]`, `hidden[]`, `open`, `closed`, `activeId`, `activeText`, `expandedIds[]`, `param{}`, `attr[[sel,name,value]]`, `text[[sel,value]]`, `href[[sel,value]]`, `count[[sel,n]]`, `value[[sel,v]]`, `hasClass[[sel,class,bool]]`, `checked`, `noOverflow`, `top[[sel,px]]` (an element's viewport top — assert it closed, open a band, assert it again: the proof an overlay did not push the page). The kit validates the file before it opens a browser and names every problem.
73
73
 
74
74
  The worked examples: `public-landing/projects.states.json` (the hub's thirteen), `public-landing/index.states.json` (the landing's four), `modules/hall-ui/public/ranks.states.json` (a second surface under its own prefix — it renders **red**, deliberately: the hall predates the 24px floor and Ranks has no `<h1>`, and the states file declares the shell's un-fixtured feeds rather than hiding the findings; idea 1000863 carries them).
75
75
 
@@ -123,7 +123,9 @@ async function runActions(page, actions = []) {
123
123
  // A state may EXPECT (render.js) and a probe must EXPECT (probe.js). Selectors are
124
124
  // evaluated in the page; scalars compared here. `open`/`visible` = present and not
125
125
  // hidden; `closed`/`hidden` = absent or hidden.
126
- const EXPECT_KEYS = Object.freeze(['expanded', 'dialog', 'visible', 'hidden', 'open', 'closed', 'activeId', 'activeText', 'expandedIds', 'param', 'attr', 'text', 'href', 'count', 'checked', 'value', 'hasClass', 'noOverflow']);
126
+ // `top` pins an element's viewport top in px (rounded) — the probe that proves an overlay
127
+ // did not push the page (task 1003332): assert it closed, open it, assert it again.
128
+ const EXPECT_KEYS = Object.freeze(['expanded', 'dialog', 'visible', 'hidden', 'open', 'closed', 'activeId', 'activeText', 'expandedIds', 'param', 'attr', 'text', 'href', 'count', 'checked', 'value', 'hasClass', 'noOverflow', 'top']);
127
129
  function validateExpect(expect, at) {
128
130
  if (expect === undefined) return [];
129
131
  if (!expect || typeof expect !== 'object' || Array.isArray(expect)) return [`${at}: an object`];
@@ -134,7 +136,7 @@ function validateExpect(expect, at) {
134
136
  for (const k of ['expanded', 'dialog', 'noOverflow']) if (expect[k] !== undefined && typeof expect[k] !== 'boolean') errors.push(`${at}.${k}: a boolean`);
135
137
  if (expect.expandedIds !== undefined && !Array.isArray(expect.expandedIds)) errors.push(`${at}.expandedIds: an array of ids`);
136
138
  if (expect.param !== undefined && (typeof expect.param !== 'object' || Array.isArray(expect.param))) errors.push(`${at}.param: an object of name → value (null = absent)`);
137
- for (const k of ['attr', 'text', 'href', 'count', 'value', 'hasClass']) if (expect[k] !== undefined && !(Array.isArray(expect[k]) && expect[k].every(Array.isArray))) errors.push(`${at}.${k}: an array of [selector, ...]`);
139
+ for (const k of ['attr', 'text', 'href', 'count', 'value', 'hasClass', 'top']) if (expect[k] !== undefined && !(Array.isArray(expect[k]) && expect[k].every(Array.isArray))) errors.push(`${at}.${k}: an array of [selector, ...]`);
138
140
  return errors;
139
141
  }
140
142
  // Runs IN THE PAGE: collects the facts the expectations compare against, plus the
@@ -183,6 +185,7 @@ function factsInPage(spec) {
183
185
  if (e.checked) { const el = q(e.checked); out.checked = el ? !!el.checked : null; }
184
186
  if (e.value) out.value = e.value.map(([s]) => { const el = q(s); return el ? el.value : null; });
185
187
  if (e.hasClass) out.hasClass = e.hasClass.map(([s, c]) => { const el = q(s); return el ? el.classList.contains(c) : null; });
188
+ if (e.top) out.top = e.top.map(([s]) => { const el = q(s); return el ? Math.round(el.getBoundingClientRect().top) : null; });
186
189
  return out;
187
190
  }
188
191
  // Pure: compare collected facts against an expectation. Returns problem strings.
@@ -208,6 +211,7 @@ function expectProblems(facts, expect = {}) {
208
211
  if (expect.checked !== undefined && facts.checked !== true) p.push(`${expect.checked} is not checked`);
209
212
  (expect.value || []).forEach(([s, v], i) => { if (facts.value[i] !== v) p.push(`${s} value "${facts.value[i]}", expected "${v}"`); });
210
213
  (expect.hasClass || []).forEach(([s, c, want = true], i) => { if (facts.hasClass[i] !== want) p.push(`${s} ${want ? 'lacks' : 'still has'} class "${c}"`); });
214
+ (expect.top || []).forEach(([s, v], i) => { if (facts.top[i] !== v) p.push(`${s} top ${facts.top[i]}px, expected ${v}px`); });
211
215
  if (expect.noOverflow && facts.scrollWidth > facts.clientWidth + 1) p.push(`horizontal overflow ${facts.scrollWidth} > ${facts.clientWidth}`);
212
216
  return p;
213
217
  }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.400",
3
+ "version": "1.19.401",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.400",
9
+ "version": "1.19.401",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.400",
3
+ "version": "1.19.401",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -130,6 +130,8 @@
130
130
  --label-face:var(--font-body);
131
131
  --label-size:11.5px;
132
132
  --label-space:.1em;
133
+ /* the view masthead's object (task 1003332): dark drops the contact shadow */
134
+ --mast-shadow:none;
133
135
  color-scheme:dark;
134
136
  }
135
137
  @media (prefers-color-scheme: dark){
@@ -165,6 +167,8 @@
165
167
  --label-face:var(--font-body);
166
168
  --label-size:11.5px;
167
169
  --label-space:.1em;
170
+ /* the view masthead's object (task 1003332): dark drops the contact shadow */
171
+ --mast-shadow:none;
168
172
  color-scheme:dark;
169
173
  }
170
174
  }
@@ -246,6 +250,15 @@
246
250
  --radius:16px; --radius-sm:10px; --radius-pill:999px;
247
251
  --top-h:64px;
248
252
  --gutter:clamp(16px,4vw,34px);
253
+ /* the world's object for the hub's view masthead (task 1003332): the apex
254
+ shares the landing's plates because it is the same world (DESIGN.md
255
+ "Materials"); the hub takes the alpha CUTOUT in both modes — the view
256
+ wraps are stacking contexts, so a black plate under `lighten` could not
257
+ reach the page ground the way the hero's do, and a cutout needs no blend.
258
+ Root-absolute path: a url() in a custom property resolves against the
259
+ DOCUMENT. Light gives the object the world's one contact shadow. */
260
+ --plate-set-cut:url(/assets/world/r2-chrome-blobs-set-cut.webp);
261
+ --mast-shadow:drop-shadow(0 26px 30px color-mix(in srgb,var(--ink) 20%,transparent));
249
262
  /* the footer's measured height per width — the sky is 100vh minus the bar and
250
263
  this, so the map fits the viewport with no scrollbar (T2's rule); the
251
264
  toolbar row that used to be in that sum is gone (task 1003300) */
@@ -255,8 +268,13 @@
255
268
  @media (max-width:900px){
256
269
  :root{--top-h:60px;--foot-h:108px;}
257
270
  }
258
- @media (max-width:360px){
259
- :root{--foot-h:126px;}
271
+ /* with About + Docs in the row (task 1003332) the six links wrap to a second
272
+ row under ~420px, and the sentence takes a third line under ~340px — measured */
273
+ @media (max-width:420px){
274
+ :root{--foot-h:136px;}
275
+ }
276
+ @media (max-width:340px){
277
+ :root{--foot-h:154px;}
260
278
  }
261
279
 
262
280
  *{box-sizing:border-box;margin:0;padding:0;}
@@ -413,7 +431,7 @@ a.orb{cursor:pointer;}
413
431
  .foot a:hover{text-decoration:underline;}
414
432
  /* the footer doors (task 1003251): privacy · terms · contact on one row under the
415
433
  line, on every landing surface — the same link ink, the current page underlined */
416
- .foot .footNav{display:flex;justify-content:center;gap:16px;margin-top:8px;}
434
+ .foot .footNav{display:flex;flex-wrap:wrap;justify-content:center;gap:4px 16px;margin-top:8px;}
417
435
  .foot .footNav a[aria-current="page"]{text-decoration:underline;}
418
436
 
419
437
  /* ── progress strips: one visual, per-flow vocabularies (direction §4).
@@ -611,13 +629,17 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
611
629
  .switch:has(input:checked){color:var(--ink);}
612
630
 
613
631
  /* ── THE BANDS: a menu is not a floating card. It unfolds under the bar's edge,
614
- full width on the card ground, closed by one hairline, and PUSHES the page
615
- down. Cells in a hairline-separated row, each a lowercase display title over
616
- one clause; the whole cell is the target. The current view carries a 2px
617
- accent hairline on its top edge — the rail-step-lit grammar from DESIGN.md,
618
- moved onto a menu. One authored move, a 6px rise + fade on the world's
619
- exponential ease-out, dead under the kill switch at the end of this file. ── */
620
- .menuBand{position:relative;z-index:49;background:var(--bg-card);border-bottom:1px solid var(--rule);
632
+ full width on the card ground, closed by one hairline, as a LAYER OVER the
633
+ page: the page does not reflow (the owner's correction, task 1003332 — the
634
+ shipped bands pushed the page down). Absolute against the document, top at
635
+ the bar's height, so the sky, the table and the footer keep their place; the
636
+ bar (50) stays above it, the card scrim (60) above both. Cells in a
637
+ hairline-separated row, each a lowercase display title over one clause; the
638
+ whole cell is the target. The current view carries a 2px accent hairline on
639
+ its top edge — the rail-step-lit grammar from DESIGN.md, moved onto a menu.
640
+ One authored move, a 6px rise + fade on the world's exponential ease-out,
641
+ dead under the kill switch at the end of this file. ── */
642
+ .menuBand{position:absolute;left:0;right:0;top:var(--top-h);z-index:49;background:var(--bg-card);border-bottom:1px solid var(--rule);
621
643
  animation:bandIn .22s cubic-bezier(.16,1,.3,1) both;}
622
644
  @keyframes bandIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
623
645
  .bandIn{padding:20px var(--gutter) 28px;}
@@ -642,9 +664,6 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
642
664
  /* the deferred Settings slot: a place it would go, rendering nothing at all —
643
665
  not even the hairline that declares a list item in this band */
644
666
  .bandSlot{border:0;}
645
- /* the row under the hairline: About and Docs, a rung smaller than the views */
646
- .bandMore{margin-top:18px;}
647
- .bandMore .bandT{font-size:17px;font-weight:400;min-height:24px;display:inline-flex;align-items:center;}
648
667
 
649
668
  @media (max-width:900px){
650
669
  .bar{gap:10px;padding:0 12px;}
@@ -766,35 +785,63 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
766
785
  .hintbar kbd{font-family:var(--font-mono);background:color-mix(in srgb,var(--accent) 24%,transparent);
767
786
  border-radius:5px;padding:1px 6px;font-size:11px;color:var(--chrome-accent);}
768
787
 
769
- /* ── the directory table: the same rows, the same filter, and the shell is
770
- still overhead — the gate's table dropped the projects door entirely. ── */
771
- .tbl{width:100%;border-collapse:collapse;background:var(--card);border-radius:20px;overflow:hidden;
772
- box-shadow:var(--shadow-card);}
773
- .tbl th{text-align:left;font-family:var(--font-display);font-size:11.5px;font-weight:700;
774
- letter-spacing:.9px;text-transform:uppercase;color:var(--ink-faint);padding:15px 18px;
775
- border-bottom:2px solid var(--line);white-space:nowrap;}
788
+ /* ── THE TABLE IN THE WORLD (task 1003332; owner's review of the shipped chrome).
789
+ The table is a VIEW of the same world as the sky, not a panel beside it: a
790
+ masthead in the world's material, then LEDGER ROWS (DESIGN.md "Ledger rows")
791
+ — hairlines, no ground, no radius, no shadow; mono labels on the columns;
792
+ the figure voice on the numbers; the accent only on what is pressable (the
793
+ open link) or live (the status chip); the display voice on the view title.
794
+ The relationship pill states a fact and is not pressable, so it lost the
795
+ accent (the One Warm Thing Rule); a row's hover is the hairline taking ink,
796
+ the bands' grammar. The masthead is the owner's pick from three rendered
797
+ treatments (docs/design/projects-hub-direction-v2.md §9): "the ledger" —
798
+ the view title in the display voice at the left, one chrome object at the
799
+ right, nothing else. ── */
800
+ .tblMast{display:grid;grid-template-columns:1fr auto;position:relative;align-items:end;gap:26px;padding:clamp(26px,3.6vw,44px) 0 30px;}
801
+ .tblTitle{font-family:var(--font-display);font-weight:300;font-size:clamp(30px,3.6vw,48px);line-height:1;
802
+ letter-spacing:-.035em;text-transform:lowercase;color:var(--ink);text-shadow:var(--type-halo);margin:0;}
803
+ /* the object, on the world's crop maths (DESIGN.md "The crop maths"): one
804
+ rectangle of a plate, expressed against the object's own width so it scales
805
+ with the row — the set's small bead, r2-chrome-blobs-set u[.810,.955] v[.556,.840] */
806
+ .mastObj{display:block;--dia:clamp(96px,10vw,132px);width:var(--dia);aspect-ratio:.91476;background-image:var(--plate-set-cut);
807
+ background-repeat:no-repeat;background-size:calc(var(--dia) * 6.89655) auto;
808
+ background-position:calc(var(--dia) * -5.58621) calc(var(--dia) * -2.14014);filter:var(--mast-shadow);}
809
+
810
+ .tbl{width:100%;border-collapse:collapse;background:none;}
811
+ .tbl th{text-align:left;font-family:var(--label-face);font-size:var(--label-size);font-weight:400;
812
+ letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);padding:0 14px 12px;
813
+ border-bottom:1px solid var(--rule);white-space:nowrap;}
814
+ .tbl th:first-child,.tbl td:first-child{padding-left:0;}
815
+ .tbl th:last-child,.tbl td:last-child{padding-right:0;}
776
816
  /* the sort control is a real button so the keyboard can reach it; it must look
777
817
  like the header it replaced, so it inherits everything the th declares. */
778
818
  .tbl th button{all:unset;cursor:pointer;}
779
819
  .tbl th button:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
780
- .tbl td{padding:15px 18px;border-bottom:1px solid var(--line);font-size:14px;font-weight:600;
781
- color:var(--ink-soft);vertical-align:middle;}
782
- .tbl tr:last-child td{border-bottom:none;}
783
- .tbl tr:hover td{background:color-mix(in srgb,var(--accent) 5%,transparent);}
784
- /* the whole row opens the project card, so it must look like it does */
820
+ .tbl td{padding:17px 14px;border-bottom:1px solid var(--rule-soft);font-size:15px;font-weight:400;
821
+ color:var(--ink-soft);vertical-align:middle;transition:border-color .2s cubic-bezier(.2,.7,.3,1);}
822
+ /* the whole row opens the project card, so it must look like it does: the
823
+ hairline under it takes ink, as a band cell's does */
785
824
  .tbl tbody tr{cursor:pointer;}
786
- .tbl .pname{display:flex;align-items:center;gap:11px;}
787
- .tbl .pdot{width:11px;height:11px;border-radius:50%;background:var(--acc);flex:0 0 auto;
788
- box-shadow:0 0 0 3px color-mix(in srgb,var(--acc) 22%,transparent);}
789
- .tbl .ptitle{font-family:var(--font-display);font-weight:700;font-size:15px;color:var(--ink);text-decoration:none;}
825
+ .tbl tbody tr:hover td{border-bottom-color:var(--ink-faint);}
826
+ .tbl .pname{display:flex;align-items:center;gap:12px;}
827
+ .tbl .pdot{width:10px;height:10px;border-radius:50%;background:var(--acc);flex:0 0 auto;}
828
+ .tbl .ptitle{font-family:var(--font-display);font-weight:500;font-size:17px;letter-spacing:-.015em;color:var(--ink);text-decoration:none;}
790
829
  .tbl a.ptitle:hover{text-decoration:underline;}
791
- .tbl .ptag{font-size:12.5px;color:var(--ink-faint);font-weight:600;}
792
- .tbl .num{font-family:var(--font-mono);font-weight:500;}
830
+ .tbl .ptag{font-size:13.5px;color:var(--ink-soft);font-weight:400;}
831
+ .tbl .num{font-family:var(--font-display);font-weight:300;font-size:22px;letter-spacing:-.02em;color:var(--ink);
832
+ font-variant-numeric:tabular-nums;}
793
833
  .tbl .tags{display:flex;gap:6px;flex-wrap:wrap;}
794
834
  .tbl .tags span{font-family:var(--font-mono);font-size:10.5px;padding:2px 8px;border-radius:999px;
795
- border:1px solid var(--line);color:var(--ink-faint);white-space:nowrap;}
796
- .tbl .pill{display:inline-block;font-family:var(--font-mono);font-size:11px;padding:3px 10px;
797
- border-radius:999px;background:var(--accent-soft);color:var(--accent-deep);}
835
+ border:1px solid var(--rule);color:var(--ink-faint);white-space:nowrap;}
836
+ .tbl .pill{display:inline-block;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);
837
+ text-transform:uppercase;padding:3px 9px;border-radius:999px;border:1px solid var(--rule);color:var(--ink-faint);}
838
+ /* the row's door: the accent on the one pressable thing in the row, as text
839
+ (the footer-link grammar), lowercase like every control in the world */
840
+ .rowGo{display:inline-flex;align-items:center;min-height:24px;padding:3px 0;font-family:var(--font-body);font-size:15px;
841
+ text-transform:lowercase;color:var(--accent-deep);text-decoration:underline;text-underline-offset:4px;
842
+ text-decoration-color:color-mix(in srgb,var(--accent) 55%,transparent);white-space:nowrap;
843
+ transition:text-decoration-color .2s cubic-bezier(.2,.7,.3,1);}
844
+ .rowGo:hover{text-decoration-color:var(--accent-deep);}
798
845
  .tbl td.right{text-align:right;}
799
846
  /* Nothing in an action or status cell may wrap: "Manage →" breaking after the
800
847
  space and the chip's recency dropping to a second line both read as a broken
@@ -807,8 +854,14 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
807
854
  .tbl .colType{width:1%;white-space:nowrap;}
808
855
  /* the declared-type tag (task 1002772) — its own vocabulary: not the relationship
809
856
  .pill, not the liveness .chip. Two different facts never wear the same chrome. */
810
- .typeTag{font-family:var(--font-mono);font-size:10.5px;font-weight:500;color:var(--ink-soft);border:1px solid var(--line);border-radius:999px;padding:2px 9px;white-space:nowrap;}
857
+ .typeTag{font-family:var(--font-mono);font-size:10.5px;font-weight:500;color:var(--ink-soft);border:1px solid var(--rule);border-radius:999px;padding:2px 9px;white-space:nowrap;}
811
858
  @media (max-width:780px){.tbl .colTags,.tbl .colType{display:none;}}
859
+ @media (max-width:900px){
860
+ .tblMast{gap:16px;padding:22px 0 20px;}
861
+ .tblTitle{font-size:clamp(28px,7.4vw,34px);}
862
+ .mastObj{--dia:clamp(72px,18vw,96px);}
863
+ .tbl th,.tbl td{padding-left:10px;padding-right:10px;}
864
+ }
812
865
 
813
866
  /* ── the living sky (task 1002762): camera, spotlight, hover card ──
814
867
  The camera is ONE transform on .skyCam; orbs carry only their own physics
@@ -191,10 +191,11 @@ main:focus{outline:none;box-shadow:none}
191
191
  @media (prefers-color-scheme:dark){:root:not([data-mode="light"]) .sun{display:block}
192
192
  :root:not([data-mode="light"]) .moon{display:none}}
193
193
 
194
- /* the menu bands (.menuBand — .band is this page's content sections): not floating cards. Full width on the card ground, closed by one hairline, pushing
195
- the page down. Cells in a hairline-separated row, a lowercase display title over one clause;
196
- the whole cell is the target. One authored move, dead under world.css's kill switch. */
197
- .menuBand{position:relative;z-index:49;background:var(--bg-card);border-bottom:1px solid var(--rule);
194
+ /* the menu bands (.menuBand — .band is this page's content sections): not floating cards. Full width on the card ground, closed by one hairline, a LAYER
195
+ over the page under the bar — the page does not reflow (owner, 2026-08-28; task 1003332). Cells in a
196
+ hairline-separated row, a lowercase display title over one clause; the whole cell is the target.
197
+ One authored move, dead under world.css's kill switch. */
198
+ .menuBand{position:absolute;left:0;right:0;top:var(--top-h);z-index:49;background:var(--bg-card);border-bottom:1px solid var(--rule);
198
199
  animation:bandIn .22s cubic-bezier(.16,1,.3,1) both}
199
200
  @keyframes bandIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
200
201
  .bandIn{padding:20px 26px 28px}
@@ -207,8 +208,6 @@ main:focus{outline:none;box-shadow:none}
207
208
  .bandCell:hover .bandT{color:var(--ink)}
208
209
  .bandCell:hover{border-top-color:var(--ink-faint)}
209
210
  .bandC{display:block;margin-top:5px;font-size:13.5px;line-height:1.45;color:var(--ink-soft)}
210
- .bandMore{margin-top:18px}
211
- .bandMore .bandT{font-size:17px;font-weight:400;min-height:24px;display:inline-flex;align-items:center}
212
211
  .bandProfile{display:flex;align-items:flex-end;justify-content:space-between;gap:34px;flex-wrap:wrap}
213
212
  .bandWho{display:flex;align-items:center;gap:10px;padding-top:15px;border-top:1px solid var(--rule-soft);
214
213
  flex:0 1 auto;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);
@@ -843,9 +842,10 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
843
842
  </header>
844
843
 
845
844
  <!-- the two bands: full-width, on the card ground, closed by one hairline; they unfold under the
846
- bar's edge and push the page down. One disclosure component, two instances (the script at the
847
- foot of the page): Escape closes and returns focus to the trigger, the arrows walk the items,
848
- Tab out closes, a click outside closes, opening one closes the other. -->
845
+ bar's edge as a layer over the page, which does not reflow (task 1003332). One disclosure
846
+ component, two instances (the script at the foot of the page): Escape closes and returns focus
847
+ to the trigger, the arrows walk the items, Tab out closes, a click outside closes, opening one
848
+ closes the other. -->
849
849
  <div class="menuBand" id="menuProjects" data-menu="projects" hidden>
850
850
  <div class="bandIn">
851
851
  <ul class="bandRow">
@@ -854,12 +854,8 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
854
854
  <li class="bandCell" id="mineCell" hidden><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own or build on</span></li>
855
855
  <li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
856
856
  </ul>
857
- <!-- under a hairline: what this is, and the docs (§5's About cell; goal 1000063's About page
858
- takes the anchor over when it lands) -->
859
- <ul class="bandRow bandMore">
860
- <li class="bandCell"><a class="bandT" href="#about">About</a></li>
861
- <li class="bandCell"><a class="bandT" href="/docs">Docs</a></li>
862
- </ul>
857
+ <!-- About and Docs are site pages, not project views: they live in the footer row with
858
+ Privacy / Terms / Contact (owner, 2026-08-28; task 1003332). -->
863
859
  </div>
864
860
  </div>
865
861
  <div class="menuBand" id="menuProfile" data-menu="profile" hidden>
@@ -1096,6 +1092,8 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
1096
1092
  <footer>
1097
1093
  <div class="footrow">
1098
1094
  <p><!--apex-only:start-->A non-profit, capture-resistant commons. <!--apex-only:end-->Built by agents, on Cloud&nbsp;Bongos. <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">Open source (AGPL-3.0)</a>.</p>
1095
+ <a href="#about">About</a>
1096
+ <a href="/docs">Docs</a>
1099
1097
  <a href="/privacy">Privacy</a>
1100
1098
  <a href="/terms">Terms</a>
1101
1099
  <a href="/contact">Contact</a>
@@ -3,9 +3,12 @@
3
3
  "page": "/",
4
4
  "surface": "public-landing",
5
5
  "probes": [
6
- { "name": "landing: Enter opens Projects on the first item", "auth": true, "width": 1440,
6
+ { "name": "landing closed: the main content starts under the bar", "auth": true, "width": 1440,
7
+ "steps": [],
8
+ "expect": { "expanded": false, "top": [["#main", 72]] } },
9
+ { "name": "landing: Enter opens Projects on the first item; the band overlays and the hero does not move (task 1003332)", "continue": true,
7
10
  "steps": [["focus", "#projTrig"], ["press", "Enter"], ["wait", 150]],
8
- "expect": { "open": "#menuProjects", "activeText": "Sky" } },
11
+ "expect": { "open": "#menuProjects", "activeText": "Sky", "top": [["#main", 72]] } },
9
12
  { "name": "landing: arrows walk the visible items (Your projects shows signed in)", "continue": true,
10
13
  "steps": [["press", "ArrowDown"], ["press", "ArrowDown"]],
11
14
  "expect": { "activeText": "Your projects" } },
@@ -14,16 +17,16 @@
14
17
  "expect": { "closed": "#menuProjects", "activeId": "projTrig" } },
15
18
  { "name": "landing: the profile band closes the Projects band", "continue": true,
16
19
  "steps": [["click", "#projTrig"], ["wait", 120], ["click", "#who .trig"], ["wait", 120]],
17
- "expect": { "open": "#menuProfile", "closed": "#menuProjects" } },
20
+ "expect": { "open": "#menuProfile", "closed": "#menuProjects", "top": [["#main", 72]] } },
18
21
  { "name": "landing: the profile band names the person; sign out keeps the honest href", "continue": true,
19
22
  "steps": [],
20
23
  "expect": { "href": [["#menuProfile a.signout", "/api/bongos/auth/logout"]], "text": [["#bandWho", "builder"]], "count": [["#bandWho img", 1]] } },
21
24
  { "name": "landing: a click outside closes", "continue": true,
22
25
  "steps": [["clickAt", 700, 700], ["wait", 120]],
23
26
  "expect": { "closed": "#menuProfile", "expanded": false } },
24
- { "name": "landing: the band deep-links the hub views + About + Docs", "continue": true,
27
+ { "name": "landing: the band deep-links the hub views; About and Docs are footer links (task 1003332)", "continue": true,
25
28
  "steps": [["click", "#projTrig"], ["wait", 120]],
26
- "expect": { "count": [["#menuProjects a", 6]], "href": [["#menuProjects .bandCell:nth-child(1) a", "/projects?view=map"], ["#menuProjects .bandCell:nth-child(2) a", "/projects?view=map&sub=table"], ["#menuProjects a[href=\"#about\"]", "#about"], ["#menuProjects a[href=\"/docs\"]", "/docs"]] } },
29
+ "expect": { "count": [["#menuProjects a", 4], ["#menuProjects a[href=\"/docs\"]", 0]], "href": [["#menuProjects .bandCell:nth-child(1) a", "/projects?view=map"], ["#menuProjects .bandCell:nth-child(2) a", "/projects?view=map&sub=table"], ["footer a[href=\"#about\"]", "#about"], ["footer a[href=\"/docs\"]", "/docs"]] } },
27
30
  { "name": "landing: #about exists", "continue": true,
28
31
  "steps": [["press", "Escape"]],
29
32
  "expect": { "count": [["#about", 1]] } },
@@ -418,9 +418,10 @@ summary{min-height:24px;padding:3px 0;}
418
418
  </header>
419
419
 
420
420
  <!-- ══ THE BANDS ══ The menus are not floating cards. Each is a full-width band
421
- that unfolds under the bar's edge and PUSHES the page down, so a
422
- destination is a place the page opens into rather than a list that hovers
423
- over it. One component, two instances (see the bands block in the script):
421
+ that unfolds under the bar's edge as a layer OVER the page — the page does
422
+ not reflow (owner, 2026-08-28; task 1003332) — so a destination is a place
423
+ that opens under the bar rather than a list that hovers beside it. One
424
+ component, two instances (see the bands block in the script):
424
425
  Escape closes and returns focus to the trigger, the arrows walk the items,
425
426
  a click outside closes, opening one closes the other. Plain links in a
426
427
  disclosure (aria-expanded + aria-controls, no menu role), so what is
@@ -435,12 +436,8 @@ summary{min-height:24px;padding:3px 0;}
435
436
  <li class="bandCell"><a class="bandT" href="?view=mine" data-nav="mine">Your projects</a><span class="bandC">the ones you own or build on</span></li>
436
437
  <li class="bandCell"><a class="bandT" href="?view=new" data-nav="new">New project</a><span class="bandC">five questions and a home</span></li>
437
438
  </ul>
438
- <!-- under a hairline (§5's About cell): what this is, and the docs. About is the landing's
439
- own explanation until goal 1000063's About page takes the anchor over. -->
440
- <ul class="bandRow bandMore">
441
- <li class="bandCell"><a class="bandT" href="/#about">About</a></li>
442
- <li class="bandCell"><a class="bandT" href="/docs">Docs</a></li>
443
- </ul>
439
+ <!-- About and Docs are site pages, not project views: they live in the
440
+ footer row with Privacy / Terms / Contact (owner, 2026-08-28; task 1003332). -->
444
441
  </div>
445
442
  </div>
446
443
  <div class="menuBand" id="menuProfile" data-menu="profile" hidden>
@@ -508,6 +505,15 @@ summary{min-height:24px;padding:3px 0;}
508
505
  </div>
509
506
 
510
507
  <div class="wrap viewWrap" id="sub-table-body" hidden>
508
+ <!-- ══ THE TABLE IN THE WORLD (task 1003332) ══ The table is a view of the same
509
+ world as the sky: a masthead in the world's material — the view title in
510
+ the display voice and one chrome object — then ledger rows. The owner's
511
+ pick from three rendered treatments (docs/design/projects-hub-direction-v2.md
512
+ §9): "the ledger", with the title cut to two words. -->
513
+ <div class="tblMast">
514
+ <h2 class="tblTitle">every project</h2>
515
+ <span class="mastObj" aria-hidden="true"></span>
516
+ </div>
511
517
  <table class="tbl">
512
518
  <thead>
513
519
  <tr>
@@ -533,7 +539,7 @@ summary{min-height:24px;padding:3px 0;}
533
539
  <td class="right num" id="selfBuilders">—</td>
534
540
  <td class="colTags" id="selfTags">—</td>
535
541
  <td class="right colStatus"><span class="chip calm"><span class="cdot"></span>—</span></td>
536
- <td class="colGo"><a class="btn small ghost" href="/">Open →</a></td>
542
+ <td class="colGo"><a class="rowGo" href="/">Open →</a></td>
537
543
  </tr>
538
544
  <!--apex-only:end-->
539
545
  </tbody>
@@ -1070,7 +1076,7 @@ summary{min-height:24px;padding:3px 0;}
1070
1076
 
1071
1077
  </main>
1072
1078
 
1073
- <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on <span data-brand-product-inline>Cloud&nbsp;Bongos</span>. <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">Open source (AGPL-3.0)</a>.<nav class="footNav" aria-label="Site"><a href="/privacy">Privacy</a><a href="/terms">Terms</a><a href="/contact">Contact</a><!--apex-only:start--><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a><!--apex-only:end--></nav></footer>
1079
+ <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on <span data-brand-product-inline>Cloud&nbsp;Bongos</span>. <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">Open source (AGPL-3.0)</a>.<nav class="footNav" aria-label="Site"><a href="/#about">About</a><a href="/docs">Docs</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a><a href="/contact">Contact</a><!--apex-only:start--><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a><!--apex-only:end--></nav></footer>
1074
1080
 
1075
1081
  <script>
1076
1082
  /* Brand-pack fill (R57): the server injects window.__BRANDING__ ahead of this
@@ -1485,7 +1491,7 @@ summary{min-height:24px;padding:3px 0;}
1485
1491
  '<td class="right colStatus">' + (p._inst ? chipFor(p._inst)
1486
1492
  : '<span class="chip calm"><span class="cdot"></span>—</span>') + '</td>' +
1487
1493
  '<td class="colGo">' + (safeHttpUrl(p.origin)
1488
- ? '<a class="btn small ghost" href="' + esc(safeHttpUrl(p.origin)) + '" target="_blank" rel="noopener">Open →</a>'
1494
+ ? '<a class="rowGo" href="' + esc(safeHttpUrl(p.origin)) + '" target="_blank" rel="noopener">Open →</a>'
1489
1495
  : '') + '</td>' +
1490
1496
  '</tr>';
1491
1497
  }).join(''));
@@ -5543,10 +5549,14 @@ summary{min-height:24px;padding:3px 0;}
5543
5549
  /* Boot. The view is set FIRST, from the URL, so the page is navigable before
5544
5550
  any request resolves — /projects?view=new must land on the create view even
5545
5551
  if the identity call is slow or fails. */
5552
+ /* the boot query is read ONCE, before the view is set: setView rewrites the
5553
+ URL to ?view=<v> and would drop everything else (the Table deep link from
5554
+ the landing landed on the sky until task 1003332) */
5555
+ var bootQ = new URLSearchParams(location.search);
5546
5556
  setView(viewFromUrl());
5547
5557
  /* the Table item's deep link (and the landing's) names the sub-view; a view change
5548
5558
  never writes it back, so a reload from the band returns to the sky as before */
5549
- if (new URLSearchParams(location.search).get('sub') === 'table') setSub('table');
5559
+ if (bootQ.get('sub') === 'table') setSub('table');
5550
5560
 
5551
5561
  fetch(API + '/me', { credentials: 'same-origin' })
5552
5562
  .then(function (res) { if (!res.ok) throw new Error(String(res.status)); return res.json(); })
@@ -3,26 +3,29 @@
3
3
  "page": "/projects",
4
4
  "surface": "public-landing",
5
5
  "probes": [
6
- { "name": "Enter opens Projects and lands on the first item", "auth": true, "width": 1440,
6
+ { "name": "closed: the main content starts under the bar", "auth": true, "width": 1440,
7
+ "steps": [],
8
+ "expect": { "expanded": false, "top": [["#hubMain", 64]] } },
9
+ { "name": "Enter opens Projects and lands on the first item; the band overlays and the content does not move (task 1003332)", "continue": true,
7
10
  "steps": [["focus", "#projTrig"], ["press", "Enter"], ["wait", 150]],
8
- "expect": { "open": "#menuProjects", "activeText": "Sky" } },
11
+ "expect": { "open": "#menuProjects", "activeText": "Sky", "top": [["#hubMain", 64]] } },
9
12
  { "name": "ArrowDown walks to Table", "continue": true,
10
13
  "steps": [["press", "ArrowDown"]],
11
14
  "expect": { "activeText": "Table" } },
12
- { "name": "ArrowUp wraps to the last item (Docs, the About/Docs row)", "continue": true,
15
+ { "name": "ArrowUp wraps to the last item (New project; About and Docs are footer links, task 1003332)", "continue": true,
13
16
  "steps": [["press", "ArrowUp"], ["press", "ArrowUp"]],
14
- "expect": { "activeText": "Docs" } },
17
+ "expect": { "activeText": "New project" } },
15
18
  { "name": "Escape closes and returns focus to the trigger", "continue": true,
16
19
  "steps": [["press", "Escape"], ["wait", 100]],
17
20
  "expect": { "closed": "#menuProjects", "activeId": "projTrig", "expanded": false } },
18
21
  { "name": "opening the profile band closes the Projects band", "continue": true,
19
22
  "steps": [["click", "#projTrig"], ["wait", 120], ["click", "#who"], ["wait", 120]],
20
- "expect": { "open": "#menuProfile", "closed": "#menuProjects", "expandedIds": ["who"] } },
23
+ "expect": { "open": "#menuProfile", "closed": "#menuProjects", "expandedIds": ["who"], "top": [["#hubMain", 64]] } },
21
24
  { "name": "a click outside closes", "continue": true,
22
25
  "steps": [["clickAt", 700, 600], ["wait", 120]],
23
26
  "expect": { "closed": "#menuProfile", "expanded": false } },
24
27
  { "name": "Tab out of the band closes it", "continue": true,
25
- "steps": [["click", "#projTrig"], ["wait", 120], ["focus", "#menuProjects a[href=\"/docs\"]"], ["press", "Tab"], ["wait", 120]],
28
+ "steps": [["click", "#projTrig"], ["wait", 120], ["focus", "#menuProjects a[data-nav=\"new\"]"], ["press", "Tab"], ["wait", 120]],
26
29
  "expect": { "closed": "#menuProjects" } },
27
30
  { "name": "Table from the band: the table on stage, Table current, band closed", "continue": true,
28
31
  "steps": [["click", "#projTrig"], ["wait", 120], ["click", "a[data-sub=\"table\"]"], ["wait", 300]],
@@ -54,12 +57,16 @@
54
57
  { "name": "the mode toggle: the label says what the press does, and the press does it", "continue": true,
55
58
  "steps": [["clickAt", 700, 600], ["click", "#modeToggle"], ["wait", 100]],
56
59
  "expect": { "attr": [["#modeToggle", "aria-label", "Switch to light mode"], ["html", "data-mode", "dark"]] } },
60
+ { "name": "the Table deep link (the landing's band item) lands on the table, Table current (task 1003332)", "auth": true, "width": 1440, "url": "/projects?view=map&sub=table",
61
+ "steps": [["wait", 300]],
62
+ "expect": { "visible": ["#sub-table-body", ".tblMast"], "hidden": ["#sub-sky-body"], "text": [["[aria-current=\"page\"]", "Table"], [".tblTitle", "every project"]] } },
57
63
  { "name": "signed out: no chip, the door shows, no switch, the search stays", "auth": false, "width": 1440,
58
64
  "steps": [],
59
65
  "expect": { "hidden": ["#who", ".switch"], "visible": ["#signinTop", "#qWrap"] } },
60
- { "name": "signed out the Projects band is Sky / Table / New project + About / Docs (no Your projects)", "continue": true,
66
+ { "name": "signed out the Projects band is Sky / Table / New project (no Your projects); About and Docs are in the footer row", "continue": true,
61
67
  "steps": [["click", "#projTrig"], ["wait", 120]],
62
- "expect": { "count": [["#menuProjects .bandCell:not([hidden])", 5]], "hidden": ["#menuProjects [data-nav=\"mine\"]"] } },
68
+ "expect": { "count": [["#menuProjects .bandCell:not([hidden])", 3], ["#menuProjects a[href=\"/docs\"]", 0]], "hidden": ["#menuProjects [data-nav=\"mine\"]"],
69
+ "href": [[".footNav a[href=\"/#about\"]", "/#about"], [".footNav a[href=\"/docs\"]", "/docs"]] } },
63
70
  { "name": "signed out: the card still closes on Escape (nothing throws with no session)", "continue": true,
64
71
  "steps": [["press", "Escape"], ["wait", 100], ["dispatchClick", "#orbField a.worldOrb:not(#selfOrb)"], ["wait", 400], ["press", "Escape"], ["wait", 150]],
65
72
  "expect": { "dialog": false, "closed": "#menuProjects" } },
@@ -69,9 +76,9 @@
69
76
  { "name": "390: Escape folds the row and returns focus to the magnifier", "continue": true,
70
77
  "steps": [["press", "Escape"], ["wait", 100]],
71
78
  "expect": { "activeId": "qToggle", "hasClass": [["#qWrap", "is-open", false]] } },
72
- { "name": "390: an opening band folds the search", "continue": true,
79
+ { "name": "390: an opening band folds the search and overlays the sky (the content does not move)", "continue": true,
73
80
  "steps": [["click", "#qToggle"], ["wait", 100], ["click", "#projTrig"], ["wait", 150]],
74
- "expect": { "hasClass": [["#qWrap", "is-open", false]], "open": "#menuProjects" } },
81
+ "expect": { "hasClass": [["#qWrap", "is-open", false]], "open": "#menuProjects", "top": [["#hubMain", 60]] } },
75
82
  { "name": "390: a solo Shift tap opens the fold and focuses the field, without overflow", "continue": true,
76
83
  "steps": [["press", "Escape"], ["wait", 100], ["press", "Shift"], ["wait", 150]],
77
84
  "expect": { "hasClass": [["#qWrap", "is-open", true]], "activeId": "mapQ", "noOverflow": true } }
@@ -14,7 +14,7 @@
14
14
  "in-map-kbd": { "auth": true, "actions": [["focus", "#projTrig"], ["press", "Enter"], ["wait", 350], ["press", "ArrowDown"], ["wait", 120]], "expect": { "expanded": true, "activeText": "Table" } },
15
15
  "in-map-search": { "auth": true, "actions": [["clickIfVisible", "#qToggle"], ["wait", 200], ["fill", "#mapQ", "emer"], ["wait", 300]], "expect": { "value": [["#mapQ", "emer"]] } },
16
16
  "in-map-mine-on": { "auth": true, "actions": [["check", "#onlyMine"], ["wait", 300]], "expect": { "checked": "#onlyMine" } },
17
- "in-table": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 250], ["click", "a[data-sub=\"table\"]"], ["wait", 400]], "expect": { "visible": ["#sub-table-body"], "closed": "#menuProjects" } },
17
+ "in-table": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 250], ["click", "a[data-sub=\"table\"]"], ["wait", 400]], "expect": { "visible": ["#sub-table-body", ".tblMast", ".mastObj"], "closed": "#menuProjects" } },
18
18
  "in-mine": { "auth": true, "url": "/projects?view=mine", "expect": { "hidden": ["#qWrap"] } },
19
19
  "in-new": { "auth": true, "url": "/projects?view=new", "expect": { "hidden": ["#qWrap"] } },
20
20
  "out-new": { "auth": false, "url": "/projects?view=new", "expect": { "hidden": ["#qWrap"] } },