@bongos/core 1.19.420 → 1.19.421

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 (34) hide show
  1. package/.bongos-core.json +68 -33
  2. package/docs/copy-inventory.md +282 -281
  3. package/docs/copy-registry.json +375 -366
  4. package/docs/design/mocks/hub/mine.html +377 -0
  5. package/docs/design/projects-hub-direction-v2.md +25 -1
  6. package/docs/file-map.md +1 -0
  7. package/docs/module-api-changelog.md +2 -0
  8. package/modules/hall-ui/public/settings.html +3 -2
  9. package/modules/hall-ui/public/settings.js +10 -6
  10. package/modules/platform-identity/account-visibility.js +94 -3
  11. package/modules/platform-identity/platform-identity.js +45 -50
  12. package/modules/platform-identity/routes/connections.js +3 -3
  13. package/modules/ui-design/kit/fixtures/featured-hub.json +1 -0
  14. package/modules/ui-design/kit/fixtures/provisioning-instances-empty.json +4 -0
  15. package/modules/ui-design/kit/fixtures/provisioning-instances.json +99 -0
  16. package/modules/ui-design/kit/lib.js +6 -2
  17. package/modules/ui-design/kit/probe.js +1 -1
  18. package/modules/ui-design/kit/render.js +1 -1
  19. package/modules/ui-design/kit/serve.js +63 -2
  20. package/package-lock.json +2 -2
  21. package/package.json +1 -1
  22. package/public-landing/assets/cosmos.css +224 -18
  23. package/public-landing/projects-empty.states.json +49 -0
  24. package/public-landing/projects-floor.states.json +63 -0
  25. package/public-landing/projects.html +112 -51
  26. package/public-landing/projects.probes.json +32 -7
  27. package/public-landing/projects.states.json +10 -6
  28. package/src/module-api.js +1 -1
  29. package/tests/account_privacy_flags.mjs +17 -10
  30. package/tests/effective_visibility_predicate.mjs +891 -0
  31. package/tests/profile_route.mjs +22 -11
  32. package/tests/projects_hub.mjs +33 -5
  33. package/tests/projects_hub_pre_uat.mjs +18 -6
  34. package/tests/ui_design_kit.mjs +32 -2
@@ -50,15 +50,10 @@
50
50
  #view-mine .hero,#view-manage .hero{max-width:none;}
51
51
 
52
52
  /* ── the manage view (task 1003041): container rhythm only — every component
53
- it draws (card, chip, addr, empty) is cosmos vocabulary ── */
54
- .backRow{margin:14px 0;font-weight:700;}
53
+ it draws (the head, the ledger, the choice cells, the rail, the doors) is
54
+ cosmos vocabulary, scoped to #view-manage there (task 1003345) ── */
55
55
  #view-manage .empty{text-align:left;}
56
56
  #view-manage .empty p{margin-bottom:0;}
57
- #view-manage .visOpt{display:flex;gap:10px;align-items:flex-start;padding:10px 12px;border:1px solid var(--rule);border-radius:10px;margin-bottom:8px;cursor:pointer;}
58
- #view-manage .visOpt:has(input:checked){border-color:var(--accent);}
59
- #view-manage .visOpt input{margin-top:4px;flex:none;}
60
- #view-manage .visOpt b{display:block;font-size:15px;}
61
- #view-manage .visOpt span{display:block;font-size:13px;color:var(--ink-soft);line-height:1.45;}
62
57
 
63
58
  /* The bongo pair on the New-project view. Component-scoped custom properties,
64
59
  never :root (C1). The drum head is SKIN — using --bg-card would turn it black
@@ -576,8 +571,8 @@ summary{min-height:24px;padding:3px 0;}
576
571
  <div class="wrap viewWrap">
577
572
  <div class="hero" style="padding-left:0;padding-right:0">
578
573
  <h1>Your projects</h1>
579
- <p class="lede">Every project you own — what it is, whether it is answering, and the controls that matter.
580
- Each card says what the platform <b>last verified</b>, and when.</p>
574
+ <p class="lede">Every project you own: what it is, whether it is answering, and the controls that matter.
575
+ Each row says what the platform <b>last verified</b>, and when.</p>
581
576
  </div>
582
577
 
583
578
  <div class="card">
@@ -617,11 +612,12 @@ summary{min-height:24px;padding:3px 0;}
617
612
  <p class="mineEmpty" id="mineEmpty" hidden>Nothing matches that. <button type="button" class="linklike" id="mineClear">Clear the filters</button></p>
618
613
 
619
614
  <section class="empty" id="empty" hidden>
620
- <p>You don't own any projects yet. Name one, pick a home for its code, and it gets an address of its own in this sky.</p>
615
+ <h2>nothing of yours in this sky yet</h2>
616
+ <p>Name a project, pick a home for its code, and it gets an address of its own here. It shows up in this list the moment you file it.</p>
621
617
  <a class="btn" href="?view=new" data-nav-to="new">Create your first project</a>
622
618
  </section>
623
619
  <ul class="projects" id="projectsList"></ul>
624
- <p class="finePrint">Buttons here ask the platform to act; each card reports only what was verified, and when. A standup keeps going even with this tab closed.</p>
620
+ <p class="finePrint">Buttons here ask the platform to act; each row reports only what was verified, and when. A standup keeps going even with this tab closed.</p>
625
621
  </section>
626
622
  </div>
627
623
  </div>
@@ -640,6 +636,19 @@ summary{min-height:24px;padding:3px 0;}
640
636
  <div class="wrap viewWrap">
641
637
  <p class="backRow"><a href="?view=mine" data-nav-to="mine">← Your projects</a></p>
642
638
 
639
+ <!-- the head stands in EVERY state (task 1003345): the page's one h1 lives
640
+ here, outside #mMain, so the signed-out, not-found and loading states
641
+ keep a visible heading (the kit's one-header-main-h1 floor). The planet
642
+ and the chip fill only once a project has rendered; the clause hides
643
+ with #mMain (cosmos, :has). -->
644
+ <div class="hero mHead" style="padding-left:0;padding-right:0">
645
+ <span id="mOrb"></span>
646
+ <div>
647
+ <div class="titleRow"><h1 id="mTitle"><span id="mName">Manage</span></h1><span id="mChip"></span></div>
648
+ <p class="lede">Its address, its repository, what the platform last <b>verified</b> about it, and the controls you have today.</p>
649
+ </div>
650
+ </div>
651
+
643
652
  <!-- signed out: the same identity-only door the mine view uses (ADR 0143) -->
644
653
  <section id="mGate" class="card" hidden>
645
654
  <p class="lede">This is a project’s management page — it answers only to the person who owns it. Sign in with GitHub to open yours.</p>
@@ -660,19 +669,18 @@ summary{min-height:24px;padding:3px 0;}
660
669
  <div class="loading" id="mLoading">Reading the project…</div>
661
670
 
662
671
  <section id="mMain" hidden>
663
- <div class="hero" style="padding-left:0;padding-right:0">
664
- <h1 id="mTitle">Manage</h1>
665
- <p class="lede">Its address, its repository, what the platform last <b>verified</b> about it — and the controls you have today.</p>
666
- </div>
667
-
668
- <div class="card">
669
- <div class="projHead"><span class="projName" id="mName">…</span><span id="mChip"></span></div>
670
- <div class="projMeta" id="mMeta" hidden></div>
671
- <div class="addr" id="mAddr" hidden></div>
672
- <p class="finePrint" id="mCreated" hidden></p>
672
+ <!-- the ledger of facts (task 1003345): each fact keeps its id and fills
673
+ as one label · value pair; a fact with no answer hides — never a dash -->
674
+ <div class="card" id="mFacts">
675
+ <div class="ledger">
676
+ <div id="mMeta" hidden></div>
677
+ <div id="mAddr" hidden></div>
678
+ <div id="mCreated" hidden></div>
679
+ <div id="mPlanet" hidden></div>
680
+ </div>
673
681
  <!-- the card's own story, on the project's own page (task 1003232): the
674
- per-flow strip, the honest snag, and the doors — the same
675
- stripFor/errMarkup/canRetry/canRevive the list card reads -->
682
+ per-flow rail, the honest snag, and the doors — the same
683
+ stripFor/errMarkup/canRetry/canRevive the list row reads -->
676
684
  <div id="mStrip"></div>
677
685
  <div class="projMsg err" id="mErr" hidden></div>
678
686
  <div class="projMsg" id="mMsg" role="status" aria-live="polite"></div>
@@ -684,8 +692,8 @@ summary{min-height:24px;padding:3px 0;}
684
692
  private direction is refused (server + copy) until the private-repo
685
693
  deploy fix lands, because the deploy pull cannot read a private repo
686
694
  (walk finding N4). -->
687
- <div class="card" id="mRepoVisibility">
688
- <h2 style="font-family:var(--font-display);font-size:19px;margin-bottom:12px;">Repository visibility</h2>
695
+ <div class="card mSec" id="mRepoVisibility">
696
+ <h2>Repository visibility</h2>
689
697
  <div id="mRepoVis"><p class="finePrint">Checking the repository on GitHub…</p></div>
690
698
  <!-- the limitation itself, one click away (grade follow-up task 1003077):
691
699
  the refusal above states it; this fold explains it and links the
@@ -704,8 +712,8 @@ summary{min-height:24px;padding:3px 0;}
704
712
  a change reaches a live project only by the restart the runner
705
713
  performs (ADR 0190). The fold is a SIBLING of the volatile slot (the
706
714
  #mRepoVisWhy shape) so no re-render can drop it. -->
707
- <div class="card" id="mVisibility">
708
- <h2 style="font-family:var(--font-display);font-size:19px;margin-bottom:12px;">Who can see it</h2>
715
+ <div class="card mSec" id="mVisibility">
716
+ <h2>Who can see it</h2>
709
717
  <div id="mVis"><p class="finePrint">Reading the project’s settings…</p></div>
710
718
  <details class="finePrint" id="mVisWhat">
711
719
  <summary>What “only signed-in members” changes</summary>
@@ -719,8 +727,8 @@ summary{min-height:24px;padding:3px 0;}
719
727
  RUNS is read back from its own public manifest (brand.project.
720
728
  joinability) — never from the save's reply, which is a receipt. The
721
729
  fold is a SIBLING of the volatile slot so no re-render can drop it. -->
722
- <div class="card" id="mJoinability">
723
- <h2 style="font-family:var(--font-display);font-size:19px;margin-bottom:12px;">How people join</h2>
730
+ <div class="card mSec" id="mJoinability">
731
+ <h2>How people join</h2>
724
732
  <div id="mJoin"><p class="finePrint">Reading the project’s settings…</p></div>
725
733
  <details class="finePrint" id="mJoinWhat">
726
734
  <summary>What each choice changes</summary>
@@ -736,8 +744,8 @@ summary{min-height:24px;padding:3px 0;}
736
744
  much as for a healthy one, which is why it asks for the project's own
737
745
  name typed back: it is the only control on this page that ends
738
746
  something, and the card's one-click confirm is too light for it. -->
739
- <div class="card" id="mDelete">
740
- <h2 style="font-family:var(--font-display);font-size:19px;margin-bottom:12px;">Take it offline</h2>
747
+ <div class="card mSec" id="mDelete">
748
+ <h2>Take it offline</h2>
741
749
  <div id="mDel"><p class="finePrint">Reading the project…</p></div>
742
750
  <!-- the teardown-vs-delete distinction, stated once as a SIBLING of the
743
751
  volatile slot (the #mRepoVisWhy shape) so no re-render can drop it.
@@ -1284,7 +1292,6 @@ summary{min-height:24px;padding:3px 0;}
1284
1292
  were orphans). Keeping the per-project variety is what makes a sky legible;
1285
1293
  deriving it from contract tokens is what makes it survive a re-brand. */
1286
1294
  var ORB_ACCENTS = ['var(--sea)', 'var(--terracotta)', 'var(--gold-soft)', 'var(--sun-lit)', 'var(--accent)'];
1287
- var ORB_GLOWS = ORB_ACCENTS.map(function (t) { return 'color-mix(in srgb,' + t + ' 45%,transparent)'; });
1288
1295
  /* THE PLANET TEMPLATES (task 1003319; the materials, task 1003337). A project's
1289
1296
  orb is one of the world's three whole objects (blob / pair / bead) in one of
1290
1297
  four materials (chrome — the first round, named by omission — frost, gold,
@@ -2009,7 +2016,7 @@ summary{min-height:24px;padding:3px 0;}
2009
2016
  function loadCatalog() {
2010
2017
  fetch(API + '/projects/featured', { credentials: 'same-origin' })
2011
2018
  .then(function (res) { if (!res.ok) throw new Error('catalog'); return res.json(); })
2012
- .then(function (data) { catalog = (data && data.projects) || []; assignAccents(); applyMapFilter(); })
2019
+ .then(function (data) { catalog = (data && data.projects) || []; assignAccents(); applyMapFilter(); repaintPlanets(); })
2013
2020
  .catch(function () {
2014
2021
  /* a child instance has no catalog module at all — say so plainly
2015
2022
  rather than leave an empty sky with no explanation */
@@ -4249,19 +4256,57 @@ summary{min-height:24px;padding:3px 0;}
4249
4256
  return !!inst.open_intent; /* e.g. a queued teardown or domain-attach on an active instance */
4250
4257
  }
4251
4258
 
4252
- /* ── the orb: the gate's project sphere, glow keyed to verification ────── */
4253
-
4259
+ /* ── the planet on a row (task 1003345): the SAME object the sky shows. A
4260
+ catalog-matched instance takes its catalog row's template and accent
4261
+ (curated, or position-walked by assignAccents); an owned-only one walks
4262
+ by its slug, the rule the map's appended entries use — so one project is
4263
+ one object in both views and never re-colours between them. ── */
4264
+ function planetOf(inst) {
4265
+ var h = inst.domain ? hostOf(inst.domain) : '';
4266
+ for (var i = 0; h && i < catalog.length; i++) {
4267
+ if (hostOf(catalog[i].origin) === h) {
4268
+ return { tpl: templateOf(catalog[i]), acc: accentOf(catalog[i]), curated: ORB_TEMPLATES.indexOf(catalog[i].template) !== -1 };
4269
+ }
4270
+ }
4271
+ return { tpl: slugTemplate(inst.slug), acc: ORB_ACCENTS[slugAccent(inst.slug)], curated: false };
4272
+ }
4273
+ /* shape[-material]-style → the data attributes cosmos.css draws from (the map's rule) */
4274
+ function planetAttrs(pl) {
4275
+ var parts = pl.tpl.split('-');
4276
+ return ' data-template="' + esc(pl.tpl) + '" data-shape="' + esc(parts[0]) +
4277
+ '" data-material="' + esc(parts.length === 3 ? parts[1] : 'chrome') +
4278
+ '" data-style="' + esc(parts[parts.length - 1]) + '"';
4279
+ }
4280
+ var PLANET_SHAPE_WORDS = { blob: 'the blob', pair: 'the bongo pair', bead: 'the bead' };
4281
+ var PLANET_STYLE_WORDS = { tint: 'tinted', wash: 'washed', plain: 'plain', chrome: 'plain' };
4282
+ /* the planet as a clause for the manage ledger: the accent as a dot, then shape, material, style */
4283
+ function planetWords(pl) {
4284
+ var parts = pl.tpl.split('-');
4285
+ var material = parts.length === 3 ? parts[1] : 'chrome';
4286
+ return '<span class="swDot" style="--sw:' + pl.acc + '" aria-hidden="true"></span>' +
4287
+ esc((PLANET_SHAPE_WORDS[parts[0]] || parts[0]) + ' in ' + material + ', ' +
4288
+ (PLANET_STYLE_WORDS[parts[parts.length - 1]] || parts[parts.length - 1]));
4289
+ }
4290
+ /* the catalog can land after a view rendered — the rows and the manage head take its planets then */
4291
+ function repaintPlanets() {
4292
+ Object.keys(byId).forEach(function (id) { renderCard(byId[id]); });
4293
+ if (manageInst) paintManagePlanet(manageInst);
4294
+ }
4295
+
4296
+ /* ── the row's object: the planet, its state on the object (ghost = torn
4297
+ down, ashen = failed its check, dim = a stale probe); only a live
4298
+ project's planet is a door to its site ── */
4254
4299
  function orbFor(inst) {
4255
- var k = slugAccent(inst.slug);
4256
- var cls = 'orb';
4300
+ var pl = planetOf(inst);
4301
+ var cls = 'worldOrb';
4257
4302
  if (inst.status === 'torn_down' || inst.status === 'tearing_down') cls += ' ghost';
4258
4303
  else if (inst.status === 'error' || (inst.status === 'active' && inst.last_verified_up === false)) cls += ' ashen';
4259
4304
  else if (inst.status === 'active' && !freshUp(inst)) cls += ' dim';
4260
- var style = '--acc:' + ORB_ACCENTS[k] + ';--glow:' + ORB_GLOWS[k];
4305
+ var attrs = planetAttrs(pl) + ' style="--acc:' + pl.acc + ';--d:64px"';
4261
4306
  if (inst.status === 'active' && inst.domain) {
4262
- return '<a class="' + cls + '" style="' + style + '" href="https://' + esc(inst.domain) + '" target="_blank" rel="noopener" title="Open ' + esc(inst.slug) + '" aria-label="Open ' + esc(inst.slug) + '"></a>';
4307
+ return '<a class="' + cls + '"' + attrs + ' href="https://' + esc(inst.domain) + '" target="_blank" rel="noopener" title="Open ' + esc(inst.slug) + '" aria-label="Open ' + esc(inst.slug) + '"><span class="disc"></span></a>';
4263
4308
  }
4264
- return '<span class="' + cls + '" style="' + style + '" aria-hidden="true"></span>';
4309
+ return '<span class="' + cls + '"' + attrs + ' aria-hidden="true"><span class="disc"></span></span>';
4265
4310
  }
4266
4311
 
4267
4312
  /* ── progress strips: each flow speaks its own stages (direction §4).
@@ -4747,6 +4792,17 @@ summary{min-height:24px;padding:3px 0;}
4747
4792
  encodeURIComponent('/projects?view=manage&id=' + manageId);
4748
4793
  }
4749
4794
 
4795
+ /* one label · value pair of the manage ledger */
4796
+ function ledgerRow(r) { return '<span class="k">' + r[0] + '</span><span class="v">' + r[1] + '</span>'; }
4797
+ /* the head's planet and the ledger's planet row (task 1003345) — one resolution
4798
+ (planetOf) for both, so the head, the row and the sky can never disagree; the
4799
+ planet row is the slot the picker (task 1003347) fills */
4800
+ function paintManagePlanet(inst) {
4801
+ var pl = planetOf(inst);
4802
+ $('mOrb').innerHTML = orbFor(inst);
4803
+ $('mPlanet').innerHTML = ledgerRow(['planet', planetWords(pl) + (pl.curated ? '' : '<small>chosen for it by the platform.</small>')]);
4804
+ $('mPlanet').hidden = false;
4805
+ }
4750
4806
  function renderManage(inst) {
4751
4807
  manageInst = inst;
4752
4808
  $('mLoading').hidden = true;
@@ -4757,20 +4813,22 @@ summary{min-height:24px;padding:3px 0;}
4757
4813
  /* the ONE chip site of this view — chipFor() or nothing, so the manage
4758
4814
  page can never tell a different liveness story than the card (C6) */
4759
4815
  $('mChip').innerHTML = chipFor(inst);
4760
- var meta = [];
4816
+ paintManagePlanet(inst);
4817
+ var rows = [];
4761
4818
  if (inst.target_ref) {
4762
- meta.push('repo <a href="https://github.com/' + esc(inst.target_ref) +
4763
- '" target="_blank" rel="noopener"><code>' + esc(inst.target_ref) + '</code></a>');
4819
+ rows.push(['repository', '<a href="https://github.com/' + esc(inst.target_ref) +
4820
+ '" target="_blank" rel="noopener">' + esc(inst.target_ref) + '</a>']);
4764
4821
  }
4765
- if (inst.hosting_shape) meta.push(esc(SHAPE_LABEL[inst.hosting_shape] || inst.hosting_shape));
4766
- if (inst.onboard_mode === 'adopt') meta.push('adopted repo');
4767
- $('mMeta').innerHTML = meta.map(function (m) { return '<span>' + m + '</span>'; }).join('');
4768
- $('mMeta').hidden = !meta.length;
4822
+ var server = inst.hosting_shape ? esc(SHAPE_LABEL[inst.hosting_shape] || inst.hosting_shape) : '';
4823
+ if (inst.onboard_mode === 'adopt') server += (server ? ', ' : '') + 'adopted repo';
4824
+ if (server) rows.push(['server', server]);
4825
+ $('mMeta').innerHTML = rows.map(ledgerRow).join('');
4826
+ $('mMeta').hidden = !rows.length;
4769
4827
  var addr = addressRow(inst);
4770
- $('mAddr').innerHTML = addr;
4828
+ $('mAddr').innerHTML = addr ? ledgerRow(['address', addr]) : '';
4771
4829
  $('mAddr').hidden = !addr;
4772
4830
  /* a fact with no answer hides — never a dash (the card rule) */
4773
- $('mCreated').textContent = inst.created_at ? 'Created ' + listedDate(inst.created_at) : '';
4831
+ $('mCreated').innerHTML = inst.created_at ? ledgerRow(['created', esc(listedDate(inst.created_at))]) : '';
4774
4832
  $('mCreated').hidden = !inst.created_at;
4775
4833
  /* fetch-free like everything else here, so the once-a-minute tick may call
4776
4834
  it: the delete section reads only status + open_intent, both already in
@@ -4780,8 +4838,7 @@ summary{min-height:24px;padding:3px 0;}
4780
4838
  only, so a queued settings push shows up on the tick without a reload */
4781
4839
  renderVisState(inst);
4782
4840
  renderJoinState(inst);
4783
- /* the page names its project — in the heading and in the tab */
4784
- $('mTitle').textContent = inst.slug || 'Manage';
4841
+ /* the page names its project — the heading holds #mName; and in the tab */
4785
4842
  document.title = (inst.slug ? inst.slug + ' — ' : '') + BASE_TITLE;
4786
4843
  /* the card's own story, on the project's own page (task 1003232): the
4787
4844
  per-flow strip, the honest snag, and the doors the list card has */
@@ -4880,6 +4937,10 @@ summary{min-height:24px;padding:3px 0;}
4880
4937
  $('mGate').hidden = true;
4881
4938
  $('mMissing').hidden = true;
4882
4939
  $('mLoading').hidden = false;
4940
+ /* the head stands in every state — a fresh load must not wear the last project's name */
4941
+ $('mName').textContent = 'Manage';
4942
+ $('mChip').innerHTML = '';
4943
+ $('mOrb').innerHTML = '';
4883
4944
  }
4884
4945
  var asked = manageId; /* a stale answer must never paint a newer project */
4885
4946
  fetch(API + '/provisioning/instances/' + encodeURIComponent(manageId), { credentials: 'same-origin' })
@@ -1,7 +1,8 @@
1
1
  {
2
- "_": "The hub's interaction contract for modules/ui-design/kit/probe.js (task 1003320): the disclosure bands, the views, the card, the fold under 480, signed out. A probe with `continue` keeps the previous page — the contract is a sequence.",
2
+ "_": "The hub's interaction contract for modules/ui-design/kit/probe.js (task 1003320): the disclosure bands, the views, the card, the fold under 480, signed out. A probe with `continue` keeps the previous page — the contract is a sequence. The manage flows (task 1003345): attach, restart, teardown (the row and the ender), revive, retry and join drive the stub's receipts end to end, so the makeover cannot silently break a door. The catalog is featured-hub.json (stub.fixtures): the signed-in stub owns Demo, and an owned orb's plain click opens its manage view, never its card — the card probes open the landing's other rows and Tidewater, the un-owned floor row.",
3
3
  "page": "/projects",
4
4
  "surface": "public-landing",
5
+ "stub": { "fixtures": { "featured": "featured-hub" } },
5
6
  "probes": [
6
7
  { "name": "closed: the main content starts under the bar", "auth": true, "width": 1440,
7
8
  "steps": [],
@@ -46,7 +47,7 @@
46
47
  "steps": [["click", "#menuProfile a[data-nav-to=\"mine\"]"], ["wait", 300]],
47
48
  "expect": { "param": { "view": "mine" }, "closed": "#menuProfile", "text": [["[aria-current=\"page\"]", "Your projects"]] } },
48
49
  { "name": "an orb opens the card", "continue": true,
49
- "steps": [["back"], ["wait", 400], ["dispatchClick", "#orbField a.worldOrb:not(#selfOrb)"], ["wait", 400]],
50
+ "steps": [["back"], ["wait", 400], ["dispatchClick", "#orbField a.worldOrb:not(#selfOrb):not([data-manage])"], ["wait", 400]],
50
51
  "expect": { "dialog": true } },
51
52
  { "name": "Escape closes the card when no band is open", "continue": true,
52
53
  "steps": [["press", "Escape"], ["wait", 150]],
@@ -59,11 +60,11 @@
59
60
  "text": [["#cardBuilders", "11"], ["#cardShipped", "0"], ["#cardRel", "you build here"], ["#cardRuns", "amazonprimea.com"], ["#cardFaces .facesMore", "+6"], ["#cardHall", "Builders' hall"]],
60
61
  "href": [["#cardHall", "https://builders.amazonprimea.com/"], ["#cardOpen", "https://amazonprimea.com/"]] } },
61
62
  { "name": "the ladder: a project that answered nothing is the card that shipped before (disc, name, tagline, address, listed, one door)", "continue": true,
62
- "steps": [["press", "Escape"], ["wait", 150], ["dispatchClick", "#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]"], ["wait", 500]],
63
+ "steps": [["press", "Escape"], ["wait", 150], ["dispatchClick", "#orbField a.worldOrb[data-origin=\"https://tidewater.cloudbongos.com\"]"], ["wait", 500]],
63
64
  "expect": { "dialog": true, "hasClass": [["#projCard", "has-art", false]],
64
65
  "hidden": ["#cardArt", "#cardFaces", "#cardFigs", "#cardBuildersFig", "#cardShippedFig", "#cardHall", "#cardRel", "#cardTypeL", "#cardOwnerL"],
65
66
  "visible": ["#cardDot", "#cardName", "#cardTag", "#cardRuns", "#cardListed", "#cardOpen"],
66
- "text": [["#cardRuns", "demo.cloudbongos.com"]] } },
67
+ "text": [["#cardRuns", "tidewater.cloudbongos.com"]] } },
67
68
  { "name": "the hall door is address-gated: a member of a project whose hall is its own site gets no door; the capped figure reads N+", "continue": true,
68
69
  "steps": [["press", "Escape"], ["wait", 150], ["dispatchClick", "#orbField a.worldOrb[data-origin=\"https://emersonian-circles.cloudbongos.com\"]"], ["wait", 500]],
69
70
  "expect": { "dialog": true, "hidden": ["#cardArt", "#cardFaces", "#cardHall"], "visible": ["#cardFigs", "#cardBuildersFig", "#cardShippedFig", "#cardOpen"],
@@ -74,7 +75,7 @@
74
75
  "text": [["#cardRel", "yours"]], "href": [["#cardOpen", "/"]] } },
75
76
  { "name": "the table: the hall glyph rides the door cell on the one member row whose hall is elsewhere, and on the platform's row", "continue": true,
76
77
  "steps": [["press", "Escape"], ["wait", 150], ["click", "#projTrig"], ["wait", 120], ["click", "a[data-sub=\"table\"]"], ["wait", 300]],
77
- "expect": { "dialog": false, "visible": ["#selfHall"], "count": [["#mapRows tr[data-generated] .rowHall", 1], ["#mapRows tr[data-generated] td", 18]],
78
+ "expect": { "dialog": false, "visible": ["#selfHall"], "count": [["#mapRows tr[data-generated] .rowHall", 1], ["#mapRows tr[data-generated] td", 42]],
78
79
  "href": [["#mapRows tr[data-generated] .rowHall", "https://builders.amazonprimea.com/"]] } },
79
80
  { "name": "back to the sky for the rest of the sequence", "continue": true,
80
81
  "steps": [["click", "#projTrig"], ["wait", 120], ["click", "a[data-sub=\"sky\"]"], ["wait", 300]],
@@ -93,7 +94,7 @@
93
94
  "expect": { "count": [["#menuProjects .bandCell:not([hidden])", 3], ["#menuProjects a[href=\"/docs\"]", 0]], "hidden": ["#menuProjects [data-nav=\"mine\"]"],
94
95
  "href": [[".footNav a[href=\"/#about\"]", "/#about"], [".footNav a[href=\"/docs\"]", "/docs"]] } },
95
96
  { "name": "signed out: the card still closes on Escape (nothing throws with no session)", "continue": true,
96
- "steps": [["press", "Escape"], ["wait", 100], ["dispatchClick", "#orbField a.worldOrb:not(#selfOrb)"], ["wait", 400], ["press", "Escape"], ["wait", 150]],
97
+ "steps": [["press", "Escape"], ["wait", 100], ["dispatchClick", "#orbField a.worldOrb:not(#selfOrb):not([data-manage])"], ["wait", 400], ["press", "Escape"], ["wait", 150]],
97
98
  "expect": { "dialog": false, "closed": "#menuProjects" } },
98
99
  { "name": "390: the magnifier opens the row and focuses the field", "auth": true, "width": 390,
99
100
  "steps": [["click", "#qToggle"], ["wait", 150]],
@@ -106,6 +107,30 @@
106
107
  "expect": { "hasClass": [["#qWrap", "is-open", false]], "open": "#menuProjects", "top": [["#hubMain", 60]] } },
107
108
  { "name": "390: a solo Shift tap opens the fold and focuses the field, without overflow", "continue": true,
108
109
  "steps": [["press", "Escape"], ["wait", 100], ["press", "Shift"], ["wait", 150]],
109
- "expect": { "hasClass": [["#qWrap", "is-open", true]], "activeId": "mapQ", "noOverflow": true } }
110
+ "expect": { "hasClass": [["#qWrap", "is-open", true]], "activeId": "mapQ", "noOverflow": true } },
111
+ { "name": "manage flows (task 1003345): the address fold on a live row records a domain", "auth": true, "width": 1440, "url": "/projects?view=mine",
112
+ "steps": [["wait",600],["click","li[data-inst=\"101\"] details.attachFold summary"],["fill","#dom-101","demo.example.com"],["click","[data-attach=\"101\"]"],["wait",600]],
113
+ "expect": {"text":[["#msg-101","Address recorded."]],"hasClass":[["#msg-101","ok",true]]} },
114
+ { "name": "manage flows: restart on an unreachable row asks; the row re-reads the project and shows the queued rail, the door leaves while the intent is open", "auth": true, "width": 1440, "url": "/projects?view=mine",
115
+ "steps": [["wait",600],["click","[data-restart=\"105\"]"],["wait",900]],
116
+ "expect": {"text":[["li[data-inst=\"105\"] .strip .pnote","A restart is queued — this card updates when it starts."],["li[data-inst=\"105\"] .strip .stages li.now .slbl","Restart queued"]],"visible":["li[data-inst=\"105\"] .strip","li[data-inst=\"105\"] .worldOrb.ashen"],"hidden":["[data-restart=\"105\"]"]} },
117
+ { "name": "manage flows: tear it down asks twice on a row — the truth first, then the door", "auth": true, "width": 1440, "url": "/projects?view=mine",
118
+ "steps": [["wait",600],["click","[data-teardown=\"101\"]"],["wait",200]],
119
+ "expect": {"visible":["li[data-inst=\"101\"] .confirmRow","[data-teardown-yes=\"101\"]","[data-teardown-no=\"101\"]"],"hidden":["[data-teardown=\"101\"]"]} },
120
+ { "name": "manage flows: the second press queues the teardown; the row re-reads the project and shows the wind-down rail, the door leaves", "continue": true,
121
+ "steps": [["click","[data-teardown-yes=\"101\"]"],["wait",900]],
122
+ "expect": {"text":[["li[data-inst=\"101\"] .strip .pnote","A teardown is queued — this card updates when it starts."]],"visible":["li[data-inst=\"101\"] .strip.flowDown"],"hidden":["[data-teardown=\"101\"]","[data-teardown-yes=\"101\"]"]} },
123
+ { "name": "manage flows: bring it back on a torn-down row re-files the project", "auth": true, "width": 1440, "url": "/projects?view=mine",
124
+ "steps": [["wait",600],["click","[data-revive=\"104\"]"],["wait",600]],
125
+ "expect": {"text":[["#msg-104","Queued. The platform picks it up from here."]],"hasClass":[["#msg-104","ok",true]],"visible":["li[data-inst=\"104\"] .worldOrb.ghost"]} },
126
+ { "name": "manage flows: try again on a snagged row re-files it too", "auth": true, "width": 1440, "url": "/projects?view=mine",
127
+ "steps": [["wait",600],["click","[data-retry=\"103\"]"],["wait",600]],
128
+ "expect": {"text":[["#msg-103","Queued. The platform picks it up from here."]],"hasClass":[["#msg-103","ok",true]]} },
129
+ { "name": "manage flows: how people join — pick a cell, save, the receipt lands under the choice", "auth": true, "width": 1440, "url": "/projects?view=manage&id=101",
130
+ "steps": [["wait",900],["check","input[name=\"mJoinPick\"][value=\"apply\"]"],["click","[data-join-save]"],["wait",600]],
131
+ "expect": {"text":[["#mJoinMsg","Saved. The project restarts to apply it."],["#mName","demo"]],"checked":"input[name=\"mJoinPick\"][value=\"apply\"]","visible":["#mOrb .worldOrb","#mFacts .ledger .k"]} },
132
+ { "name": "manage flows: the ender asks for the name typed back, queues the teardown, and the page re-reads the project as queued", "auth": true, "width": 1440, "url": "/projects?view=manage&id=105",
133
+ "steps": [["wait",900],["click","[data-del-ask]"],["wait",200],["fill","#mDelName","mooring"],["wait",100],["click","[data-del-yes]"],["wait",900]],
134
+ "expect": {"text":[["#mDel .addr","A teardown is queued for this project."],["#mName","mooring"]],"hidden":["#mDelName","[data-del-ask]"],"visible":["#mStrip .strip.flowDown"]} }
110
135
  ]
111
136
  }
@@ -1,28 +1,32 @@
1
1
  {
2
- "_": "The hub's states for modules/ui-design/kit/render.js (task 1003320): name → url + actions + expectations. `auth` picks the signed-in stub. Rendered dark only at 1440/390/320 (the apex pins data-mode=\"dark\", task 1003336) and audited against the platform floors. The card states (task 1003301) walk the poster's ladder: the full poster, the artless two-up band, the all-null floor, the platform's own card.",
2
+ "_": "The hub's states for modules/ui-design/kit/render.js (task 1003320): name → url + actions + expectations. `auth` picks the signed-in stub. Rendered dark only at 1440/390/320 (the apex pins data-mode=\"dark\", task 1003336) and audited against the platform floors. The card states (task 1003301) walk the poster's ladder: the full poster, the artless two-up band, the all-null floor, the platform's own card. The signed-in stub OWNS Demo (provisioning-instances.json, task 1003345), and an owned orb's plain click opens its manage view, never its card — so the all-null floor card renders from projects-floor.states.json on the hub's own catalog (featured-hub.json, one un-owned null row more).",
3
3
  "page": "/projects",
4
4
  "surface": "public-landing",
5
5
  "modes": ["dark"],
6
6
  "modeQuery": false,
7
- "ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects", "/provisioning/"],
8
- "ignoreConsole": ["/api/(gds|bongos)/me", "\\b401\\b", "Unauthorized"],
7
+ "ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects", "/provisioning/instances/999", "/provisioning/instances/101$", "/api/gds/instance$"],
8
+ "ignoreConsole": ["/api/(gds|bongos)/me", "\\b401\\b", "\\b404\\b", "Unauthorized"],
9
9
  "ignoreControls": [".skip", ".vh"],
10
10
  "states": {
11
11
  "out-map": { "auth": false, "expect": { "expanded": false } },
12
- "in-map": { "auth": true, "expect": { "expanded": false, "count": [["#orbField .worldOrb[data-shape][data-style]", 4]], "attr": [["#selfOrb", "data-template", "pair-chrome"], ["#orbField a.worldOrb[data-origin=\"https://amazonprimea.com\"]", "data-template", "bead-wash"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-shape", "pair"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-material", "gold"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-template", "pair-gold-wash"], ["#orbField a.worldOrb[data-origin=\"https://emersonian-circles.cloudbongos.com\"]", "data-template", "bead-obsidian-plain"]] } },
12
+ "in-map": { "auth": true, "expect": { "expanded": false, "count": [["#orbField .worldOrb[data-shape][data-style]", 7]], "attr": [["#selfOrb", "data-template", "pair-chrome"], ["#orbField a.worldOrb[data-origin=\"https://amazonprimea.com\"]", "data-template", "bead-wash"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-shape", "pair"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-material", "gold"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-template", "pair-gold-wash"], ["#orbField a.worldOrb[data-origin=\"https://emersonian-circles.cloudbongos.com\"]", "data-template", "bead-obsidian-plain"]] } },
13
13
  "in-map-projects": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 350]], "expect": { "expanded": true, "open": "#menuProjects" } },
14
14
  "in-map-profile": { "auth": true, "actions": [["click", "#who"], ["wait", 350]], "expect": { "expanded": true, "open": "#menuProfile" } },
15
15
  "in-map-kbd": { "auth": true, "actions": [["focus", "#projTrig"], ["press", "Enter"], ["wait", 350], ["press", "ArrowDown"], ["wait", 120]], "expect": { "expanded": true, "activeText": "Table" } },
16
16
  "in-map-search": { "auth": true, "actions": [["clickIfVisible", "#qToggle"], ["wait", 200], ["fill", "#mapQ", "emer"], ["wait", 300]], "expect": { "value": [["#mapQ", "emer"]] } },
17
17
  "in-map-mine-on": { "auth": true, "actions": [["check", "#onlyMine"], ["wait", 300]], "expect": { "checked": "#onlyMine" } },
18
18
  "in-table": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 250], ["click", "a[data-sub=\"table\"]"], ["wait", 400]], "expect": { "visible": ["#sub-table-body", ".tblMast", ".mastObj", "#selfHall"], "closed": "#menuProjects", "count": [["#mapRows tr[data-generated] .rowHall", 1]] } },
19
- "in-mine": { "auth": true, "url": "/projects?view=mine", "expect": { "hidden": ["#qWrap"] } },
19
+ "in-mine": { "auth": true, "url": "/projects?view=mine", "expect": { "hidden": ["#qWrap", "#empty"], "visible": ["#mineBar", "#projectsList", "#projectsList .proj .worldOrb .disc"], "count": [["#projectsList .proj", 5], ["#projectsList .proj.gone", 1], ["#projectsList .proj .worldOrb[data-shape][data-material][data-style]", 5], ["#projectsList .proj .strip", 1], ["#projectsList .proj .worldOrb.ashen", 2], ["#projectsList [data-restart]", 1]] } },
20
+ "in-manage": { "auth": true, "url": "/projects?view=manage&id=101", "expect": { "visible": ["#mMain", "#mChip .chip", "#mOrb .worldOrb", "#mFacts .ledger .k", "#mActs"], "hidden": ["#qWrap", "#mGate", "#mMissing", "#mErr"], "text": [["#mName", "demo"], ["#mTitle", "demo"]] } },
21
+ "in-manage-snag": { "auth": true, "url": "/projects?view=manage&id=103", "expect": { "visible": ["#mMain", "#mErr", "#mActs"], "text": [["#mName", "tidepool"]] } },
22
+ "in-manage-gone": { "auth": true, "url": "/projects?view=manage&id=104", "expect": { "visible": ["#mMain", "#mActs"], "hidden": ["#mErr"], "text": [["#mName", "old-lantern"]] } },
23
+ "in-manage-missing": { "auth": true, "url": "/projects?view=manage&id=999", "expect": { "visible": ["#mMissing"], "hidden": ["#mMain", "#mGate"] } },
24
+ "out-manage": { "auth": false, "url": "/projects?view=manage&id=101", "expect": { "visible": ["#mGate"], "hidden": ["#mMain", "#mMissing"] } },
20
25
  "in-new": { "auth": true, "url": "/projects?view=new", "expect": { "hidden": ["#qWrap"] } },
21
26
  "out-new": { "auth": false, "url": "/projects?view=new", "expect": { "hidden": ["#qWrap"] } },
22
27
  "out-map-projects": { "auth": false, "actions": [["click", "#projTrig"], ["wait", 350]], "expect": { "expanded": true, "hidden": ["#menuProjects [data-nav=\"mine\"]"] } },
23
28
  "card": { "auth": true, "actions": [["dispatchClick", "#orbField a.worldOrb[data-origin=\"https://amazonprimea.com\"]"], ["wait", 900]], "expect": { "dialog": true, "hasClass": [["#projCard", "has-art"]], "visible": ["#cardArt", "#cardFaces", "#cardBuildersFig", "#cardShippedFig", "#cardOpen", "#cardHall"], "text": [["#cardBuilders", "11"], ["#cardShipped", "0"], ["#cardRel", "you build here"], ["#cardRuns", "amazonprimea.com"], ["#cardFaces .facesMore", "+6"]], "count": [["#cardFaces img", 5]], "href": [["#cardHall", "https://builders.amazonprimea.com/"]] } },
24
29
  "card-noart": { "auth": true, "actions": [["dispatchClick", "#orbField a.worldOrb[data-origin=\"https://emersonian-circles.cloudbongos.com\"]"], ["wait", 600]], "expect": { "dialog": true, "hasClass": [["#projCard", "has-art", false]], "hidden": ["#cardArt", "#cardFaces", "#cardHall"], "visible": ["#cardBuildersFig", "#cardShippedFig", "#cardOpen"], "text": [["#cardShipped", "30+"], ["#cardRel", "you build here"]] } },
25
- "card-floor": { "auth": true, "actions": [["dispatchClick", "#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]"], ["wait", 600]], "expect": { "dialog": true, "hidden": ["#cardArt", "#cardFaces", "#cardFigs", "#cardHall", "#cardRel", "#cardTypeL", "#cardOwnerL"], "visible": ["#cardOpen", "#cardRuns", "#cardListed"] } },
26
30
  "card-self": { "auth": true, "actions": [["dispatchClick", "#selfOrb"], ["wait", 600]], "expect": { "dialog": true, "hidden": ["#cardArt"], "visible": ["#cardBuildersFig", "#cardShippedFig", "#cardFaces", "#cardOpen", "#cardHall"], "text": [["#cardRel", "yours"]] } }
27
31
  },
28
32
  "reducedMotion": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 100]] }
package/src/module-api.js CHANGED
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
33
33
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
34
34
  // the entry to that file. Look for a version's history there, not here.
35
35
  // ---------------------------------------------------------------------------
36
- const CORE_VERSION = '1.19.420'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
36
+ const CORE_VERSION = '1.19.421'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
37
37
 
38
38
  // A namespaced logger so a module's log lines are attributable + consistent.
39
39
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -284,16 +284,23 @@ test('the discoverable toggle names EXACTLY what it grants — the D3 sliver, na
284
284
  'the section ships hidden — it reveals only where the hub module answers');
285
285
  });
286
286
 
287
- test('no toggle claims a protection this task has not built — the read paths are still open', () => {
287
+ test('each toggle claims exactly what is enforced — visibility in force, recruiting not yet', () => {
288
288
  const js = readFileSync(join(ROOT, 'modules/hall-ui/public/settings.js'), 'utf8');
289
289
  const html = readFileSync(join(ROOT, 'modules/hall-ui/public/settings.html'), 'utf8');
290
- // Storing a flag is not enforcing it (task 1002967 wires the read paths). A
291
- // builder flipping this for personal safety must not be told they are hidden
292
- // while GET /profiles/:handle still serves the page.
293
- assert.match(html, /saved to your account now\. They start applying as[\s\S]*nothing is hidden yet/,
294
- 'the section states plainly that the choice is recorded, not yet in force');
295
- assert.doesNotMatch(js, /Private hides the page|is private — hidden from everyone/,
296
- 'no present-tense promise that the page is already hidden');
297
- assert.match(js, /Private will hide the page from everyone but you/,
298
- 'the row says what the choice WILL do');
290
+ // A toggle may promise only what a read path actually enforces, and the two
291
+ // rows are at different stages. Task 1002967 wired VISIBILITY into every
292
+ // public read, so its row moved to the present tense here — and it must also
293
+ // say the page 404s to the OWNER, because that is the surprising half of D2
294
+ // (the owner reads their record through the own-scope side door instead).
295
+ // DISCOVERABILITY is still stored-only: no recruiting surface reads it, so a
296
+ // present-tense promise there would be the "protection you do not have" this
297
+ // assertion pair exists to prevent.
298
+ assert.match(js, /Private hides the page from everyone — including you/,
299
+ 'visibility is enforced now, so the row says so — and names the 404-to-self');
300
+ assert.doesNotMatch(js, /Private will hide the page/,
301
+ 'the future tense outlived its truth the moment resolveHandle joined the predicate');
302
+ assert.match(html, /profile page setting applies now[\s\S]*Recruiter discoverability is saved to your[\s\S]*starts applying as the recruiting surfaces land/,
303
+ 'the section note splits the two: one in force, one recorded and waiting');
304
+ assert.doesNotMatch(js, /Recruiters looking for builders (?:can|are able to) find you\b/,
305
+ 'no present-tense promise about recruiting reach — nothing reads that flag yet');
299
306
  });