diff --git a/PENDING.md b/PENDING.md index cf2aebc1..98acb1a7 100644 --- a/PENDING.md +++ b/PENDING.md @@ -8,7 +8,7 @@ what belongs to whom and coordinate. Naming convention: `YYYY-MM-DD:short-slug` for date-bounded work, or `parallel:` for work owned by a session that isn't the current one. -Last updated: 2026-10-28. +Last updated: 2026-10-29. --- @@ -16,7 +16,7 @@ Releases are no longer recorded here: the live version and hashes are in `../site/releases-manifest.json` and `https://dl.silentmode.st/releases-manifest.json`, and each release commit is `Theseus 0.3.x: ...` in git history. -## Staged for the next release (post-0.3.128) +## Staged for the next release (post-0.3.129) Queue of asks for the next cut. Items marked LANDED are edited in the working tree waiting for a bump + build. Items marked TODO still need the @@ -35,6 +35,84 @@ code change. is the only way through to installed users). `game.x` isn't resolving yet (no s3 pointer on-chain, no manifest); wait until the service is up before baking its description/favicon. +- **LANDED: Searchbox no longer auto-grows on focus; URL/search ratio + capped at 75/25.** Reverts the 0.3.128 "give the searchbox more room + while typing" rule — focusing the searchbox was pulling the drag handle + with it and making the ratio hard to set by hand. Changes in + [chrome.html](chrome.html): dropped the + `.urlsearch:has(.searchbox:focus-within) .searchbox { flex: 2 1 480px }` + rule and its `.14s` transition on `.searchbox`; added + `max-width: 75%` to `.urlwrap` so a stored `urlBarWidthPx` from the old + 80 % ceiling clamps on load without the user having to redrag; bumped + the drag `MIN_SHARE` 0.20 → 0.25 so new drags can't push the search + bar below 25 % of the shared budget. 0.3.128's typing-wider effect + stays off; the user owns the ratio again. +- **LANDED: Startpage sidebar runs the full page height alongside the + hero; slots fill the whole sidebar panel; empty cells double as the + "add link" surface.** [home.html](home.html): + - **Page layout restructured.** Hero + homepage are now wrapped in a + single `.page` grid (`grid-template-columns: minmax(240, 280) 1fr`) + with the sidebar ` +
+
+
+
+ + + + + + + +
+

Theseus Navigator

+
+

A browser that follows the thread. Names that live on decentralized blockchain.

+
+ + +
+

Tip: type hello.bch in the address bar above to open a decentralized site.

+ +
+
+
+
@@ -444,7 +481,7 @@ // hover of the grid. The "+" cell sits at the end so adding is always // one click away. const q = state.sections.q || []; - const slotCells = layoutSlots(q); + const slotCells = layoutSlots(q, minSlotsForFill()); const list = $("slotsList"); list.innerHTML = ""; slotCells.forEach((entry, cellIdx) => { @@ -466,15 +503,11 @@ } else { const empty = document.createElement("div"); empty.className = "slot-empty"; empty.dataset.cell = cellIdx; + empty.title = "Add link here"; wireEmptySlot(empty, cellIdx); list.appendChild(empty); } }); - const slotAdd = document.createElement("button"); - slotAdd.className = "slot-add"; slotAdd.textContent = "+"; slotAdd.title = "Add link"; - slotAdd.onclick = (e) => { e.preventDefault(); e.stopPropagation(); openModal("q", -1); }; - slotAdd.oncontextmenu = (e) => e.preventDefault(); - list.appendChild(slotAdd); // RIGHT — Silent Mode cards with sub-description under the title. const sm = state.sections.sm || []; @@ -502,11 +535,25 @@ $("ctxIconSizeVal").textContent = state.iconSize; } + // Compute how many slot rows fit the vertical space the sidebar covers. + // Sidebar height = main-col height (align-self: stretch on the aside), + // so basing the row count on main-col keeps the slot grid filling the + // whole sidebar panel instead of ending halfway down. Falls back to 24 + // (8 rows) when the layout hasn't been measured yet (first render). + function minSlotsForFill() { + const main = document.querySelector(".main-col"); + const h = main ? main.getBoundingClientRect().height : 0; + const rowH = 78; // .slot-empty height (68) + .slots-list row gap (10) + const rows = h > 0 ? Math.max(6, Math.floor(h / rowH)) : 8; + return rows * 3; + } // Lay out the q[] items into a fixed-slot grid: pinned items (with a // `slot: number`) go into that cell; unpinned items flow into the first // free cell, top-to-bottom. The grid always extends to at least one - // trailing empty row for drop space. - function layoutSlots(items, minSlots = 9) { + // trailing empty row for drop space. minSlots defaults to 18 (3 × 6) + // when nothing better is passed; render() passes minSlotsForFill() so + // the grid actually covers the whole sidebar height. + function layoutSlots(items, minSlots = 18) { const pinned = []; const unpinned = []; items.forEach((ic, i) => { const s = Number.isInteger(ic.slot) && ic.slot >= 0 ? ic.slot : null; @@ -618,6 +665,14 @@ if (!payload) return; pinItemToSlot(payload.sec, payload.i, cellIdx); }); + // Any empty cell doubles as the "add link" surface — click opens the + // link modal with this cell pre-pinned, so the user controls WHERE a + // new link lands (no dedicated + button needed). + el.addEventListener("click", (e) => { + e.preventDefault(); + openModal("q", -1, cellIdx); + }); + el.addEventListener("contextmenu", (e) => e.preventDefault()); } // Place the dragged item at `cellIdx` in the q grid, pinning it. @@ -839,8 +894,8 @@ // Add/edit modal — the same shape works for both left slots and right cards. let editKey = null; - function openModal(sec, i) { - editKey = { sec, i }; + function openModal(sec, i, pinSlot) { + editKey = { sec, i, pinSlot: Number.isInteger(pinSlot) ? pinSlot : null }; const isNew = i < 0; $("modalTitle").textContent = isNew ? "Add link" : "Edit link"; const c = isNew ? { title: "", url: "", sub: "", color: "#d6ff3d", letter: "" } @@ -869,8 +924,13 @@ letter: $("mLetter").value.trim() || null, }; const toSec = $("mSection").value; - if (!editKey || editKey.i < 0) state.sections[toSec].push(next); - else { + if (!editKey || editKey.i < 0) { + // Preserve the pinned cell index when the modal was opened from a + // specific empty slot — the new link lands exactly where the user + // clicked, not at the end. + if (editKey && editKey.pinSlot != null && toSec === "q") next.slot = editKey.pinSlot; + state.sections[toSec].push(next); + } else { const prev = state.sections[editKey.sec][editKey.i]; const merged = { ...prev, ...next, brand: prev.brand || null }; if (toSec === editKey.sec) state.sections[toSec][editKey.i] = merged; @@ -895,7 +955,7 @@ r.readAsDataURL(f); }; - // Search form (slot-add button is rendered inside slotsList per render()) + // Search form (empty cells themselves act as "add link" surfaces now; // Select-all on first click of the search field so a click lands with any // existing query ready to replace. Second click (already-focused) falls // through to native cursor placement. @@ -917,6 +977,22 @@ if (name) $("q").placeholder = "Search the web with " + name; }).catch(() => {}); } + // Running-version footer: "Theseus v0.3.X — What's new ↗". The link uses + // home.navigate so it opens release notes in this tab, same as any other + // link on the startpage. Hidden until the version resolves — kept off the + // page if the IPC isn't available (shouldn't happen in-app, but safe). + if (window.home && window.home.getVersion) { + window.home.getVersion().then((v) => { + if (!v) return; + const line = $("verLine"), a = $("verLink"); + a.textContent = "Theseus v" + v + " — What’s new ↗"; + a.addEventListener("click", (e) => { + e.preventDefault(); + window.home.navigate("https://silentmode.st/releases"); + }); + line.hidden = false; + }).catch(() => {}); + } // Container-level drop handlers accept cross-column drops onto empty space. wireContainer($("slotsList"), "q"); @@ -928,13 +1004,33 @@ let loaded = null; try { if (window.home && window.home.getCards) loaded = await window.home.getCards(); } catch {} state = ensureShape(loaded) || defaultState(); - applyBg(); render(); + applyBg(); + render(); + // The first render runs before the main column has laid out, so the + // row count comes from the fallback (24). Re-render once the layout + // settles so the slot grid fills the sidebar vertically. + requestAnimationFrame(() => render()); if (window.home && window.home.onCards) { window.home.onCards((data) => { const next = ensureShape(data); if (next) { state = next; applyBg(); render(); } }); } + // Resize the slot grid when the viewport changes so new rows appear + // (or drop off) to match the sidebar's new height. ResizeObserver on + // the main column catches both window resizes and content-driven + // height changes (e.g. new cards being added). + const main = document.querySelector(".main-col"); + if (main && window.ResizeObserver) { + let lastRows = -1, t = null; + const ro = new ResizeObserver(() => { + const rows = Math.floor(minSlotsForFill() / 3); + if (rows === lastRows) return; + lastRows = rows; + clearTimeout(t); t = setTimeout(() => render(), 80); + }); + ro.observe(main); + } })(); diff --git a/package.json b/package.json index e310ccd9..ec3d0c2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "theseus-navigator", - "version": "0.3.129", + "version": "0.3.130", "license": "MPL-2.0", "description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.", "author": "Silent Mode",