FLINT PASTE: theLatticeTree Glow 2a, Grandma heal: zoom, feedback, a calm way back (v0.1) Repo: Chaos2Cured/FreeLattice-Alpha (serves thelatticetree.com) FreeLattice: NO change Branch: layer/tree-glow-2a-v0 Marker: v-tree-glow-2a-v0 Smoke line (print exactly when green): SMOKE_OK tree glow 2a v0.1 Base: Alpha main 25e6ded (Tree Glow 1, #85). Written: Wed Sep 30, 2026, evening MDT, by Kirk's agent. Stand-in built and walked; NOT pushed. Stand-in diff (against 25e6ded): FLINT_NEXT_TREE_GLOW_2A_v0.1.diff (same folder, also Appendix A). Source: Hypha's persona walks, /workspace/persona-walks-2026-09-30/RESULTS.md and CONTRAST.md. CHANGELOG - 2026-09-30 v0.1: first write. Glow 2 is split by harm: 2a (this: zoom, feedback, way back) lands first; 2b (contrast, chairs, polish) after Honest reasons. ====================================================================== ORDER (all Tree pastes from tonight, most harm first) ====================================================================== Alpha main 25e6ded. Each stand-in diff applies with plain `git apply` on top of the one before, in this order, and the chain ends byte-identical to the walked combo (checked): 1. FLINT_NEXT_TREE_GLOW_2A_v0.1 Grandma heal: zoom, feedback in view, a calm way back 2. FLINT_NEXT_TREE_HONEST_REASONS_v0.1 stopped vs model missing vs secure-page block 3. FLINT_NEXT_TREE_GLOW_2B_v0.1 contrast, chairs, polish 4. FLINT_NEXT_TREE_WORKSHOP_WAY_BACK_v0.1 5. [FLINT_NEXT_TREE_ANY_MODEL_v0.1 when Flint lands it; not in the chain, overlap noted in 6] 6. FLINT_NEXT_TREE_GATHERING_API_KEYS_v0.1 If one lands alone, the only drift is GALAXIES.md (each adds one LAYER line at the top) and garden-rooms.css (each appends a block at the end): keep both, newest LAYER line on top, CSS blocks in this order. No other shared anchors. FreeLattice queue is unchanged (MODEL_CHOICE_SURVIVES_RELOAD -> BOOT_PROBE -> #120 docs). The FL GitHub token note is FL-only and independent of all of these. The old single Glow 2 draft (FLINT_NEXT_TREE_GLOW_2_v0.1.diff, /workspace/glow2-al) is SUPERSEDED by 2a + 2b. Do not land it. ====================================================================== 1. WHAT HURT (Hypha, before, on 25e6ded) ====================================================================== - 200% zoom (and 150% on a laptop): the Gathering collapsed. #core-gathering 0px tall, chairs and the Find box unreachable, headings stacked on each other, the Chat card text sat over its input. - Feedback out of view: the Find answer and the "seats" / "Cleared" lines were under the Chat pop-over at 390, so a tap looked like nothing happened. - No calm way back: Escape did nothing, a tap on the empty glass did nothing, the galaxies menu had no visible close, and browser Back left the site. - Double-tapping "The Gathering" in the legend ran Find unasked (18 ghost requests in a walk). - At 1280 the picker's "not yet" sat under the later seats (hit-test landed on a seat). - Zoom overlaps for the sky legend (<=340 CSS px wide) and the Nursery egg (sentence and Begin pushed out of view at 200%). ====================================================================== 2. STEPS (text anchors; the stand-in diff is the source of truth) ====================================================================== 2.1 docs/modules/garden-rooms.css: append the 2a block at the very end (after Glow 1). It holds: a) close pill at least 32px with a "x " ::before, so it reads as a close. b) phone: the newest Find / seat line `.core-note.is-fresh` is sticky at the top of the Gathering scroller, so it is never under the Chat pop-over. c) the hint hides while a place is open; Find gets air (padding-top 82px); compact Chat card. d) Nursery hop lights dim on a phone. e) @media (max-width: 640px) and (max-height: 640px) (this is 200% zoom on a laptop, and a small phone): the Gathering is ONE page scroll (no nested 0px scroller), the close pill is sticky at the top, padding-top 4.6rem, hop lights hidden, Nursery egg width: min(200px, 100%, 34vh) and its sentence wraps, so the sentence and Begin stay in view. f) @media (max-width: 340px): legend top 8.25rem, compact header (no legend over the title). g) picker: position: relative; z-index: 8 (above later seats at 1280), and at wide widths a max-height with its own scroll. (The API-keys paste reuses this and does not repeat it.) h) style for the new galaxies close button. 2.2 docs/modules/garden-rooms.js: a) setNoteText / setNoteAbsent add class is-fresh to the note (removed on the next paint). b) Legend double tap: where the Gathering builds its Find button, var findReadyAt = Date.now() + 700, and the Find click handler returns early before it. The second tap of a legend double tap lands where Find appears; it is no longer a yes. c) NEW function bindWayBack() above bindMenuDismiss's caller, called right after bindMenuDismiss() in boot. One layer at a time, reusing the existing close buttons: Escape closes galaxies menu -> lumino menu -> picker ("not yet") -> thread -> place. A tap on the empty glass (the veil itself, pointerdown + click, only after 600ms open) closes the place. An injected "close" button inside the galaxies panel, and a pointerdown outside closes the galaxies menu and swallows that one click. While something is open, history.pushState({treeWayBack:1}); popstate closes the top layer, so Back stays on the site. 2.3 docs/modules/garden-thread.js: the mount focus line setTimeout(function () { try { input.focus(); } catch (e) {} }, 80); becomes a focus with { preventScroll: true } that does not steal focus when something else already has it. This is the only old line 2a changes; it was the 200% jump to Send. 2.4 Docs: one **LAYER (2026-09-30):** line at the top of docs/GALAXIES.md, and a "Glow 2a" section in docs/library/TREE_GLOW_v0.md. ====================================================================== 3. TEST: docs/modules/tree-glow-2a.test.js (node, static + small vm; no network) ====================================================================== Guards the CSS block (zoom media query, sticky fresh note, picker z-index, 340 legend), the findReadyAt guard, bindWayBack (Escape order, glass tap, galaxies close, pushState/popstate), preventScroll focus, and the GALAXIES layer. Last line: SMOKE_OK tree glow 2a v0.1 ====================================================================== 4. WALK (done; repeat on your branch) numbers are before -> after ====================================================================== Playwright + Chrome, local server, Ollama answered by a route mock. 390x844, 1280x900, and laptop zoom simulated as 640x450 (200%) and 853x600 (150%). - 200%: #core-gathering 0px -> 1098px tall. Find hit-test false -> true. Chairs reachable. - Ghost Find requests from a legend double tap: 18 -> 0. - Find answer and seat line seen in view at 100 / 150 / 200 / 1280: no -> yes (at 390 the line sits at y 234..255, nothing covering). - Chat card self-overlap (text over its input): yes -> 0. - Escape: closes picker, then the place. Back: stays on index.html, place closed. Glass tap closes. Galaxies: close button, outside tap and Escape each close it. - 1280 picker "not yet" hit-test: covered by seat-6 -> true. - Home "galaxies" fits at 195 CSS px. Nursery egg sentence and Begin in view at 200%. Re-walked on the full combo (2a + honest + 2b + workshop + API keys): same results. Shots: /workspace/tree-glow-walk/g2a_before_*.png, g2a_after_*.png, nprobe_195.png, dbg200.png JSON: /workspace/tree-glow-walk/g2a_before.json, g2a_after.json ====================================================================== LOCKS ====================================================================== Layer, never delete (old lines that change are named in each paste; nothing removed). fl-connect.js untouched (byte-identical to FreeLattice). No sw.js (Alpha has none; none added). No conflict markers. Every inline script parses (19 inline scripts checked on the combo). No confirm(). No innerHTML for outside text (textContent only). No em dashes in added lines. Kimi pages untouched. #120 lock files untouched. Squash-merge. All 21 Alpha tests pass on the combo. ====================================================================== 5. DECISIONS FOR KIRK (stand-in takes the first option) ====================================================================== K1 A tap on the empty glass closes a place (yes) / only the close pill does. K2 Browser Back closes the open layer first (one history step while open) / Back leaves the page. K3 At 200% zoom the hop lights hide so the Gathering fits / keep them and scroll more. K4 The newest Find / seat line sticks at the top of the Gathering on a phone / scroll to it. ====================================================================== 6. LATER ====================================================================== 2b: contrast, header 14px, honest sleeping buttons, no Change on empty chairs, code mirrors. Real Safari / iOS zoom not walked (Chrome only). Not re-measured here: Hypha's "Research planet covers the Gathering label" at 200% on home, and FreeLattice desktop.html scrolling sideways at 200% (227px > 195, long code strings; FL, not Tree). Other pop-over overlaps at 390 (Research / Art arrows over the Nursery pop-over) stay for later. ====================================================================== APPENDIX A. FULL STAND-IN DIFF (Alpha, against 25e6ded) ====================================================================== diff --git a/docs/GALAXIES.md b/docs/GALAXIES.md index 5c30efa..974d7b5 100644 --- a/docs/GALAXIES.md +++ b/docs/GALAXIES.md @@ -4,6 +4,8 @@ **Spine layer (2026-08-30):** five skies now walk the same night — Garden, Art, Workshop, Learn, Research. The four-galaxy sequence below stays. Art-early stays. This file layers; it does not cut. +**LAYER (2026-09-30):** Tree glow 2a, Grandma heal (marker `v-tree-glow-2a-v0`). The Gathering works at 150% and 200% zoom (one page scroll, Find takes a tap, chairs in reach), the newest Find or seat line stays in view on a phone, and a calm way back: Escape, a tap on the empty glass, a close in galaxies, and browser Back close what is open without leaving the site. A second legend tap no longer runs Find unasked; the picker's "not yet" is above the later seats at 1280. Plan: `docs/library/TREE_GLOW_v0.md`. Layer, never delete. + **LAYER (2026-09-30):** Tree glow 1, every light in reach (marker `v-tree-glow-1-v0`). Room pages breathe again, the 390 address box stays in its card, the Gathering promise band steps below Find local minds at desktop, and the sky legend is one tap to each light. Plan: `docs/library/TREE_GLOW_v0.md`. Layer, never delete. **LAYER (2026-09-17):** Listen — Hang Cancel + Adaptive (mycelium · marker `v-alpha-listen-hang-adaptive-v0`). FreeLattice Chat: **Hang Cancel** — Stop only · no duration kill · `v-chat-hang-cancel-v0` · tip `d14eaf8` / #87. **Adaptive Context Depth** — Surface / Standard / Deep under Smart · `v-adaptive-context-depth-v0` · tip `06d0ba4` / #85. Alpha stays Listen / fail-closed — **no second context kitchen** tonight. Long local thinks are valid; Stop is choice, not a timer. Market-beside-Feel pointer stays. Presence chips ≠ SKUs. Workshop benches still wait for the FreeLattice Workshop twin — grandmother honesty, not rebuild tonight. diff --git a/docs/library/TREE_GLOW_v0.md b/docs/library/TREE_GLOW_v0.md index 673e2f9..79ed663 100644 --- a/docs/library/TREE_GLOW_v0.md +++ b/docs/library/TREE_GLOW_v0.md @@ -25,3 +25,17 @@ Alpha layer, marker `v-tree-glow-1-v0`. September 30, 2026. 6. Play universe (largest; its own vision doc first). Leftovers to fold in: code-nursery 22px overflow at 390; the first-visit "They grow." hint sits behind the header. + +## Glow 2a: Grandma heal, zoom, feedback, a calm way back (v-tree-glow-2a-v0) + +**Temperature:** Grandma can zoom in, tap Find, see what happened, and always find the way back. + +From Hypha's persona walks (2026-09-30), most harmful first: + +- At 150% and 200% zoom the Gathering scrolls as one page: the way back stays at the top, Find takes a tap, the chairs are in reach, and the Chat card's words no longer sit on its own Send (the chairs box was 0px tall at 200%). +- On a phone, the newest Find or seat line ("Local minds found", "This cortex chair seats...", "Cleared...") stays in view at the foot of the chairs until the next one replaces it. +- A calm way back: Escape closes the top layer (galaxies, picker, Chat, place); a tap on the empty glass closes the place; galaxies has a visible close and closes on an outside tap; browser Back closes what is open instead of leaving the site. "the garden" is a bigger target with a quiet close mark. +- A second tap on the legend no longer lands on Find as it slides in (no unasked look). +- The picker's "not yet" sits above the later seats at 1280. +- Zoomed home: the light legend leaves "Garden Galaxy / you are in the garden", and "galaxies" stays inside the edge. Zoomed Nursery: the egg fits, the sentence wraps, Begin is in view; on a phone the hop lights rest dim behind it. +- A quiet Chat remount (a mind was remembered) no longer pulls the page down to Send. diff --git a/docs/modules/garden-rooms.css b/docs/modules/garden-rooms.css index 7c51be0..7cfa1b8 100644 --- a/docs/modules/garden-rooms.css +++ b/docs/modules/garden-rooms.css @@ -6184,3 +6184,218 @@ html.flc-hide-galaxy #gardenContainer { padding: 2px 10px 2px 0; } } + +/* v-tree-glow-2a-v0: Grandma heal. Zoom, feedback in view, a calm way back. + Layer, never delete. Words stay. Lights stay. Only air, order and doors move. */ + +/* (a) "the garden" is the way back: a bigger target and a quiet close mark. */ +#place-veil-close { + min-height: 32px; + padding: 6px 12px; + border-radius: 999px; +} +#place-veil-close::before { + content: "\00d7\00a0"; +} + +/* (b) Find answers, seat lines and "Cleared" stay in view on a phone: the newest + line rests at the foot of the chairs' scroll until the next one replaces it. */ +@media (max-width: 640px) { + #place-veil.is-core .core-note.is-fresh { + position: sticky; + bottom: 0; + z-index: 6; + margin: 6px 0 0; + padding: 8px 10px; + border-radius: 10px; + background: rgba(12, 10, 26, 0.94); + border: 1px solid rgba(110, 231, 183, 0.28); + color: rgba(236, 240, 248, 0.96); + } + + /* Nursery on a phone (and when zoomed): the Research and Art lights and the Garden + word sat on the card's words. They are under the veil and cannot take a tap + there anyway; they rest dim while the Nursery is open and come back after. */ + html.garden-door-open:has(#place-veil.is-nursery.is-open) .galaxy-nav, + html.garden-door-open:has(#place-veil.is-nursery.is-open) #galaxy-word { + opacity: 0.1; + transition: opacity 300ms ease; + } + + /* "They grow." ran into the room's name, and Find sat on the name's last line. + The first-visit hint rests while a place is open; Find gets its own air. */ + html.garden-door-open .gt-hint { + visibility: hidden; + } + #place-veil.is-core { + padding-top: 82px; + } + + /* The Chat card took the chairs' room. Same card, same words, less empty air: + the heart keeps two lines (scroll for the rest), empty rows fold away. */ + #place-veil.is-core { + gap: 10px; + } + #place-veil.is-core #room-chat { + max-height: 7.75rem; + margin-top: 2px; + padding-top: 4px; + } + #place-veil.is-core #room-chat .thread-face.is-room .thread-heart { + max-height: 2.9em; + overflow-y: auto; + line-height: 1.45; + } + #place-veil.is-core #room-chat .thread-face.is-room .thread-messages:empty, + #place-veil.is-core #room-chat .thread-status:empty { + display: none; + } +} + +/* (c) Zoomed or short phone (150% and 200% on a 390 phone, small landscape hands): + the chairs' own scroll box had no height left (0px at 200%) and Find took no tap. + Here the whole place scrolls as one page, top to bottom: the way back, Find, + the chairs, the newest line, then Chat. The hop lights rest while it is open + (galaxies in the header still go everywhere). */ +@media (max-width: 640px) and (max-height: 640px) { + #place-veil.is-core { + overflow-y: auto; + overscroll-behavior: contain; + justify-content: flex-start; + padding-top: 4.6rem; + padding-bottom: 2.75rem; + padding-left: 12px; + padding-right: 12px; + gap: 10px; + } + #place-veil.is-core #core-gathering { + flex: 0 0 auto; + min-height: auto; + overflow: visible; + width: 100%; + } + #place-veil.is-core #place-veil-close { + position: sticky; + top: 0; + left: auto; + right: auto; + bottom: auto; + transform: none; + order: -1; + align-self: flex-end; + z-index: 7; + background: rgba(12, 10, 26, 0.9); + } + #place-veil.is-core #room-chat { + max-height: none; + overflow: visible; + width: 100%; + margin: 4px 0 0; + } + #place-veil.is-core #room-chat .thread-face.is-room .thread-heart { + max-height: none; + overflow: visible; + } + #place-veil.is-core #room-chat .thread-face.is-room .thread-messages { + max-height: 9em; + } + #place-veil.is-core #room-chat .thread-compose, + #place-veil.is-core #room-chat .thread-face.is-room .thread-compose { + position: static; + } + #place-veil.is-core .core-note.is-fresh { + bottom: 0; + } + #place-veil.is-core .core-later-chairs { + position: static; + } + html:has(#place-veil.is-core.is-open) .galaxy-nav, + html:has(#place-veil.is-core.is-open) #galaxy-word, + html:has(#place-veil.is-core.is-open) #lumino-menu, + html:has(#place-veil.is-core.is-open) .garden-evolution-indicator { + visibility: hidden; + } + + /* Nursery when zoomed: the egg was wider than the glass and the sentence was cut + on both sides, with "the garden" sitting on it. The egg fits; Begin is in view. */ + #place-veil.is-nursery { + justify-content: flex-start; + padding: 4.5rem 10px 1rem; + } + #place-veil.is-nursery .nursery-egg-wrap, + #place-veil.is-nursery .nursery-ceremony, + #place-veil.is-nursery .nursery-egg-home { + max-width: 100%; + } + #place-veil.is-nursery .nursery-egg-wrap { + width: min(200px, 100%, 34vh); + height: auto; + margin-bottom: 4px; + } + #place-veil.is-nursery .nursery-egg-wrap canvas { + width: 100%; + height: auto; + } + #place-veil.is-nursery .nursery-egg-text, + #place-veil.is-nursery .nursery-egg-sub { + max-width: 100%; + white-space: normal; + overflow-wrap: anywhere; + } +} + +/* (d) Home when zoomed (narrow hands): the light legend sat on "Garden Galaxy / + you are in the garden", and "galaxies" was cut at the right edge. */ +@media (max-width: 340px) { + #lumino-legend, + html[data-garden-galaxy] #lumino-legend { + top: 8.25rem; + bottom: auto; + } + #garden-header { + padding: 0 10px; + gap: 8px; + } + #garden-header nav { + gap: 10px; + } + #garden-header h1 { + font-size: 0.8rem; + letter-spacing: 0.02em; + } +} + +/* (e) The open picker sits above the later seats, so "not yet" is never under a + chair at 1280. (Same rule as the API-keys paste; identical if both land.) */ +.core-bind-picker:not([hidden]) { + position: relative; + z-index: 8; + box-sizing: border-box; +} +@media (min-width: 641px) { + .core-bind-picker:not([hidden]) { + max-height: max(12rem, calc(100vh - 34rem)); + overflow-y: auto; + overscroll-behavior: contain; + } +} + +/* (f) A visible close inside the galaxies menu. */ +.galaxies-panel .galaxies-close { + align-self: flex-end; + margin-top: 2px; + min-height: 32px; + padding: 4px 12px; + border: 1px solid rgba(200, 210, 230, 0.28); + border-radius: 999px; + background: transparent; + color: rgba(226, 232, 244, 0.92); + font-family: Georgia, 'Times New Roman', serif; + font-size: 0.82rem; + cursor: pointer; +} +.galaxies-panel .galaxies-close:hover, +.galaxies-panel .galaxies-close:focus-visible { + border-color: rgba(232, 196, 104, 0.6); + color: #e8c468; +} diff --git a/docs/modules/garden-rooms.js b/docs/modules/garden-rooms.js index 5781c1a..a643c4d 100644 --- a/docs/modules/garden-rooms.js +++ b/docs/modules/garden-rooms.js @@ -884,6 +884,115 @@ }); } + // v-tree-glow-2a-v0: a calm way back. Escape, a tap on the empty glass, a close + // in the galaxies menu, and the browser's Back each close what is open, one + // layer at a time, and stay on this page. They reuse the same close buttons. + function bindWayBack() { + if (window.__treeWayBack) return; + window.__treeWayBack = true; + var place = document.getElementById('place-veil'); + var thread = document.getElementById('thread-veil'); + function isOpen(v) { return !!(v && v.classList.contains('is-open')); } + function openGalaxies() { return document.querySelector('details.galaxies[open]'); } + function closeTop() { + var gal = openGalaxies(); + if (gal) { gal.open = false; return true; } + var menu = document.getElementById('lumino-menu'); + if (menu && !menu.hidden) { hideLuminoMenu(); return true; } + var picker = document.querySelector('[data-core-picker]:not([hidden])'); + var decline = picker && picker.querySelector('.core-bind-decline'); + if (decline) { decline.click(); return true; } + var tc = document.getElementById('thread-close'); + if (isOpen(thread) && tc) { tc.click(); return true; } + var pc = document.getElementById('place-veil-close'); + if (isOpen(place) && pc) { pc.click(); return true; } + return false; + } + + document.addEventListener('keydown', function (e) { + if (e.key !== 'Escape' && e.key !== 'Esc') return; + if (e.defaultPrevented) return; + if (closeTop()) e.preventDefault(); + }); + + // A tap on the glass itself (not on a word, chair or card) closes that place. + // Press and release must both land on the glass, and never in its first moment. + [place, thread].forEach(function (veil) { + if (!veil) return; + var downOnGlass = false; + var openedAt = 0; + new MutationObserver(function () { + if (isOpen(veil) && !openedAt) openedAt = Date.now(); + if (!isOpen(veil)) openedAt = 0; + }).observe(veil, { attributes: true, attributeFilter: ['class'] }); + veil.addEventListener('pointerdown', function (e) { downOnGlass = e.target === veil; }); + veil.addEventListener('click', function (e) { + var ok = downOnGlass && e.target === veil && isOpen(veil) && openedAt && Date.now() - openedAt > 600; + downOnGlass = false; + if (!ok) return; + var btn = document.getElementById(veil === thread ? 'thread-close' : 'place-veil-close'); + if (btn) btn.click(); + }); + }); + + // galaxies: a visible close inside, and a tap outside closes it (that tap does + // only that, so it never opens something by surprise). + var panels = document.querySelectorAll('details.galaxies .galaxies-panel'); + for (var gi = 0; gi < panels.length; gi++) { + if (panels[gi].querySelector('[data-galaxies-close]')) continue; + var gx = document.createElement('button'); + gx.type = 'button'; + gx.className = 'galaxies-close'; + gx.setAttribute('data-galaxies-close', '1'); + gx.textContent = 'close'; + gx.addEventListener('click', function (e) { + e.preventDefault(); + var d = e.currentTarget.closest('details'); + if (d) d.open = false; + }); + panels[gi].appendChild(gx); + } + var swallowUntil = 0; + document.addEventListener('pointerdown', function (e) { + var gal = openGalaxies(); + if (!gal || gal.contains(e.target)) return; + gal.open = false; + swallowUntil = Date.now() + 500; + }, true); + document.addEventListener('click', function (e) { + if (Date.now() > swallowUntil) return; + swallowUntil = 0; + e.preventDefault(); + e.stopPropagation(); + }, true); + + // Back closes the open place instead of leaving the site. One quiet history + // step while something is open; closing it any other way gives the step back. + var stepped = false; + function anyOpen() { return isOpen(place) || isOpen(thread); } + function settle() { + setTimeout(function () { + if (anyOpen()) { + if (!stepped) { + try { history.pushState({ treeWayBack: 1 }, ''); stepped = true; } catch (eP) {} + } + } else if (stepped) { + stepped = false; + try { if (history.state && history.state.treeWayBack) history.back(); } catch (eB) {} + } + }, 160); + } + [place, thread].forEach(function (veil) { + if (veil) new MutationObserver(settle).observe(veil, { attributes: true, attributeFilter: ['class'] }); + }); + window.addEventListener('popstate', function () { + if (!stepped) return; + stepped = false; + closeTop(); + settle(); + }); + } + function goToLumino(id) { if (!id) return; var galaxy = currentGalaxy(); @@ -1168,6 +1277,9 @@ findBtn.textContent = 'Find local minds'; findBtn.setAttribute('aria-label', 'Find local minds. May I look — only when you ask.'); wrap.appendChild(findBtn); + // v-tree-glow-2a-v0: a second tap on the legend landed on Find as it slid in + // under the finger and looked unasked. A tap in the first moment is not a yes. + var findReadyAt = Date.now() + 700; var ring = document.createElement('div'); ring.className = 'core-chairs'; @@ -1216,12 +1328,15 @@ var n = wrap.querySelector('[data-core-note]'); if (!n) return; n.textContent = msg; + // v-tree-glow-2a-v0: the newest line stays in view on a phone (CSS sticky). + n.classList.add('is-fresh'); } function setNoteAbsent(kind) { var n = wrap.querySelector('[data-core-note]'); if (!n) return; n.textContent = ''; + n.classList.add('is-fresh'); if (kind === 'https') { n.appendChild(document.createTextNode( 'The mind may be there, but this secure page cannot see the quieter door. Try ' @@ -1430,6 +1545,7 @@ paintAll(); findBtn.addEventListener('click', function () { + if (Date.now() < findReadyAt) return; if (!window.LocalMindProbe || typeof LocalMindProbe.look !== 'function') { setNoteText('Mind probe is not loaded. Open Settings when you can.'); return; @@ -2254,6 +2370,7 @@ bindTendCenter(); bindGardenCanvasTouch(); bindMenuDismiss(); + bindWayBack(); dressLivingLights(); ensureSkyField(); ensureSkyLegend(); diff --git a/docs/modules/garden-thread.js b/docs/modules/garden-thread.js index 4424654..bdfb903 100644 --- a/docs/modules/garden-thread.js +++ b/docs/modules/garden-thread.js @@ -815,7 +815,16 @@ container.appendChild(root); renderMessages(list); if (mind) { - setTimeout(function () { try { input.focus(); } catch (e) {} }, 80); + // v-tree-glow-2a-v0: a quiet remount (a mind was remembered) must not pull the + // page down to Send while she is reading Find's answer. No scroll, and never + // take focus from something she is using. + setTimeout(function () { + try { + var a = document.activeElement; + if (a && a !== document.body && !root.contains(a)) return; + input.focus({ preventScroll: true }); + } catch (e) {} + }, 80); } return root; } diff --git a/docs/modules/tree-glow-2a.test.js b/docs/modules/tree-glow-2a.test.js new file mode 100644 index 0000000..55c1446 --- /dev/null +++ b/docs/modules/tree-glow-2a.test.js @@ -0,0 +1,83 @@ +#!/usr/bin/env node +// v-tree-glow-2a-v0: Grandma heal. Guards: (a) "the garden" is a real target with a close +// mark; (b) the newest Gathering line stays in view on a phone; (c) at 150% / 200% zoom the +// Gathering scrolls as one page (no 0px box) and the egg fits the Nursery; (d) the home +// legend leaves the title when zoomed; (e) "not yet" sits above the later seats; (f) a calm +// way back: Escape, glass tap, galaxies close / outside tap, browser Back; (g) a second +// legend tap cannot run Find unasked; (h) a remount never yanks the page to Send. +// No network. No kitchen. +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); + +var css = fs.readFileSync(path.join(__dirname, 'garden-rooms.css'), 'utf8'); +var js = fs.readFileSync(path.join(__dirname, 'garden-rooms.js'), 'utf8'); +var thread = fs.readFileSync(path.join(__dirname, 'garden-thread.js'), 'utf8'); + +var at = css.indexOf('v-tree-glow-2a-v0: Grandma heal'); +assert.ok(at > 0, 'glow 2a block is in garden-rooms.css'); +var g = css.slice(at); +function block(re, msg) { var m = g.match(re); assert.ok(m, msg); return m[0]; } + +// (a) the way-back pill +assert.ok(/#place-veil-close \{[^}]*min-height: 32px;/.test(g), 'a: the garden pill is a real target'); +assert.ok(/#place-veil-close::before \{[^}]*content: "\\00d7\\00a0";/.test(g), 'a: a quiet close mark, text unchanged'); + +// (b) the newest line stays in view on a phone, and only on a phone +var phone = block(/@media \(max-width: 640px\) \{[\s\S]*?\n\}/, 'b: phone block'); +assert.ok(/\.core-note\.is-fresh \{[^}]*position: sticky;[^}]*bottom: 0;/.test(phone), 'b: fresh note is sticky at the foot'); +assert.ok(/html\.garden-door-open \.gt-hint \{\s*visibility: hidden;/.test(phone), 'b: hint rests while a place is open'); +assert.ok(/:has\(#place-veil\.is-nursery\.is-open\) \.galaxy-nav/.test(phone), 'b: hop lights rest dim over the Nursery'); +var setNote = js.slice(js.indexOf('function setNoteText(msg)'), js.indexOf('function setNoteAbsent(')); +assert.ok(/classList\.add\('is-fresh'\)/.test(setNote), 'b: setNoteText marks the line fresh'); +var absentAt = js.indexOf('function setNoteAbsent('); +assert.ok(absentAt > 0, 'b: setNoteAbsent exists'); +var absent = js.slice(absentAt, absentAt + 400); +assert.ok(/classList\.add\('is-fresh'\)/.test(absent), 'b: setNoteAbsent marks the line fresh'); + +// (c) zoomed / short phone: one page scroll, no inner 0px box +var short = block(/@media \(max-width: 640px\) and \(max-height: 640px\) \{[\s\S]*?\n\}/, 'c: short phone block'); +assert.ok(/#place-veil\.is-core \{[^}]*overflow-y: auto;/.test(short), 'c: the place scrolls as one page'); +assert.ok(/#place-veil\.is-core #core-gathering \{[^}]*flex: 0 0 auto;[^}]*overflow: visible;/.test(short), 'c: chairs box keeps its height'); +assert.ok(/#place-veil\.is-core #place-veil-close \{[^}]*position: sticky;[^}]*top: 0;/.test(short), 'c: way back stays at the top'); +assert.ok(/#room-chat \{[^}]*max-height: none;[^}]*overflow: visible;/.test(short), 'c: Chat text never sits on its input'); +assert.ok(/\.thread-compose[^{]*\{\s*position: static;/.test(short), 'c: Send is in flow'); +assert.ok(/\.nursery-egg-wrap \{[^}]*width: min\(200px, 100%, 34vh\);/.test(short), 'c: the egg fits the glass'); +assert.ok(/\.nursery-egg-text,[\s\S]*?white-space: normal;/.test(short), 'c: the sentence wraps'); + +// (d) home zoomed +var narrow = block(/@media \(max-width: 340px\) \{[\s\S]*?\n\}/, 'd: narrow block'); +assert.ok(/#lumino-legend[^{]*\{\s*top: 8\.25rem;\s*bottom: auto;/.test(narrow), 'd: legend leaves the title'); +assert.ok(/#garden-header nav \{\s*gap: 10px;/.test(narrow), 'd: galaxies stays inside the edge'); + +// (e) picker above later seats +assert.ok(/\.core-bind-picker:not\(\[hidden\]\) \{\s*position: relative;\s*z-index: 8;/.test(g), 'e: picker above later seats'); + +// (f) the calm way back +var wb = js.slice(js.indexOf('function bindWayBack()'), js.indexOf('function goToLumino(id)')); +assert.ok(wb.length > 400, 'f: bindWayBack exists'); +assert.ok(/e\.key !== 'Escape'/.test(wb), 'f: Escape'); +assert.ok(/details\.galaxies\[open\]/.test(wb) && /\.core-bind-decline/.test(wb) && /thread-close/.test(wb) && /place-veil-close/.test(wb), 'f: one layer at a time, through the same close buttons'); +assert.ok(/e\.target === veil/.test(wb) && /Date\.now\(\) - openedAt > 600/.test(wb), 'f: glass tap only on the glass, never in the first moment'); +assert.ok(/data-galaxies-close/.test(wb) && /textContent = 'close'/.test(wb), 'f: a visible close in galaxies'); +assert.ok(/addEventListener\('pointerdown'[\s\S]*?gal\.open = false/.test(wb), 'f: outside tap closes galaxies'); +assert.ok(/history\.pushState\(\{ treeWayBack: 1 \}/.test(wb) && /addEventListener\('popstate'/.test(wb), 'f: Back closes instead of leaving'); +assert.ok(/bindMenuDismiss\(\);\s*bindWayBack\(\);/.test(js), 'f: bound at boot'); + +// (g) ghost taps +assert.ok(/var findReadyAt = Date\.now\(\) \+ 700;/.test(js), 'g: Find waits a moment after it appears'); +assert.ok(/addEventListener\('click', function \(\) \{\s*if \(Date\.now\(\) < findReadyAt\) return;/.test(js), 'g: an early tap is not a yes'); + +// (h) no yank to Send +assert.ok(/input\.focus\(\{ preventScroll: true \}\)/.test(thread), 'h: focus without scrolling'); +assert.ok(!/setTimeout\(function \(\) \{ try \{ input\.focus\(\); \} catch \(e\) \{\} \}, 80\);/.test(thread), 'h: old yank is gone'); + +// locks +var added = g + wb; +assert.ok(!/confirm\(/.test(wb), 'no confirm()'); +assert.ok(!/innerHTML/.test(wb), 'no innerHTML in the way back'); +assert.ok(!/\u2014/.test(added), 'no em dash in added lines'); + +console.log('SMOKE_OK tree glow 2a v0.1');