Repository navigation
Applications: web form schema, sealed web intake, public-profile consent - #74
Open
robertovalenti wants to merge 284 commits into
Open
robertovalenti wants to merge 284 commits into
robertovalenti wants to merge 284 commits into
Conversation
The dock grows a Deck ⇄ Map toggle (persisted per device). The map view places every docked board on its claimed cell (`settings.hex`, the field the HexPicker writes) on the Mapbox basemap, drawn in TWO hexagon layers the way the dashboard map does: the faint dashed parent neighbourhood (5 resolutions coarser, findable from country zoom) under the exact cell. Tapping a parent hexagon flies to its board's cell at the resolution-appropriate zoom (goToHex-style); the hexagon badge at the cell carries the same data-dock-circle handle the deck's orbs do, so tapping it opens the board through the same morph — and closing a board in map mode irises back down onto its hexagon (the morph target lookup now waits briefly, since badges land after the basemap loads). The basemap dissolves into the space with a wequest-style edge fade. Claimed cells are cached across mounts; boards without one stay deck-only and the map hint says where to claim a place. Geolocation is now asked ONLY on an explicit My-location tap: the map view gets its own ◎ button, the HexPicker no longer auto-locates on open, and the sunset theme no longer prompts at boot — it reads the fix those buttons cache (setGeo), falling back to fixed evening hours. Verified headlessly: 0 permission asks at boot, exactly 1 after tapping ◎; badges open/close boards through the morph in map mode; a parent tap flew z13 → z15 onto the exact cell; toggle round-trips to the deck with orbs and vesica intact. 202 tests, typecheck and lint green. Claude-Session: https://claude.ai/code/session_01BMAzedDQ6m7xGgRujPQMzG Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
The map view gains the zoom-following H3 grid it was missing, drawn in the dashboard map's two layers — the zoom-matched resolution plus the next finer one fainter inside it — rebuilt on every move/zoom with a cell-count cap. All hexagon lines are thicker, and cell rings apply the dashboard's antimeridian hack (shift the positive side by -360° when a boundary jumps) so cells straddling ±180° no longer paint lines across the world. The Deck ⇄ Map switch now hovers over whichever surface is showing (frosted pill, top centre) instead of sitting in the tray; the tray keeps "+ Add a board" at the bottom in BOTH views, with the per-view hint. The map gets the HexPicker's Mapbox geocoding search as a floating pill under the switch — pick a hit and the map flies there. Verified headlessly: 730 grid cells rendered in a city viewport, searching "Milano" flew the map to 45.46,9.19, toggle/add/hint placed as designed in both views. 202 tests, typecheck and lint green. Claude-Session: https://claude.ai/code/session_01BMAzedDQ6m7xGgRujPQMzG Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
h3's polygon fill returns nothing for a viewport wider than half the globe — exactly the fully zoomed-out view — so the grid and the lens presence subscriptions vanished. `viewportCells` now serves world-wide boxes from the global cell set, re-wraps longitudes Mapbox unwraps past ±180, and keeps latitudes off the poles. The map sits in a rounded, dark-grounded card with an inset vignette, basemap edge to edge; the hex grid carries a per-cell `fade` so it dissolves before the rim (edgeFade) while lit lens cells stay solid. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…n, place search Companion to 91db231 (which shipped DockMap + maplens but not the modules they import — this restores a buildable HEAD): - holosphere: subscribeLensPresence, the dock map's per-cell presence channel. Gun replays a lens's records to the FIRST subscriber only, so the channel taps every record it hears (onItem) for the cell panel to seed from; graph-metadata junk (HAM fragments under ">", soul refs) is filtered via looksLikeRecord so it neither lights a cell nor reaches the panel. normalizeSub exported for the panel's own live subscription. - i18n: the 18 dashboard lens labels + map panel strings (en/it/es). - DockView: place search extracted into PlaceSearch.svelte; orbs sized in rem measured from the live root font so the vesica matches the drawn circles at every viewport width. - layout: the open/close morph waits briefly for the map badge to mount before animating, instead of jumping when the basemap lags. Verified headlessly against HolonsDebug: seeded quests light exactly their cell, tapping it lists them in the panel; junk-lit cells are gone. Claude-Session: https://claude.ai/code/session_01BMAzedDQ6m7xGgRujPQMzG Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…hexagon The post-it cards hash a seed into the six-note palette (a task by its category, a role by its title). The same hash applied to the holon id now picks the holon's colour, and that one colour is used everywhere the holon appears: a faint wash over its board card, its orb in the dock, its hexagon (cell, parent cell, marker) on the map, and the glow edge of every card mirrored from it via federation or a hologram. The dock's separate hue hash is gone. A caretaker can override it with a `color` field on the holon's settings document. Core owns the algorithm and the override rule (core/settings/color.ts: colorHash, pickColor, holonColor, readHolonColor, saveHolonColor, spec beside it); HolonSettings carries the optional field. The kiosk keeps learned overrides in lib/palette (displayed holon, its partners, every docked board) and Settings gains a per-holon "Board colour" picker with an Automatic reset (en/it/es). Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
… to record details Top bar: search | Deck⇄Map switch | My-location — the switch stays centred whatever the side slots hold; the locate button moves up from the map's bottom corner (DockMap exports locate()) and the lens chips take the whole bottom edge. Cell panel: each listed item is now a tap-through to a detail card read straight off the record's JSON — well-known fields first (status, when, where, amount+currency, participants by name, link, origin holon…), dates in the kiosk locale, coordinates as lat/lng, then the remaining scalar fields; graph plumbing, pictures and headline echoes are dropped. itemDetails() lives in lib/maplens with tests; labels in en/it/es. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
On the map view the bar is inset to the card's margin plus the lens chips' breathing room, so the search and the My-location button never overhang the rounded frame. Under 40rem the switch keeps the top line and search + locate wrap onto a second line, the search taking the width the button leaves. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…dDB and file adapters Phase 1 of removing Gun: a standalone store that mirrors signed kind-30078 events (records + per-author envelopes + backlinks + cursors), NIP-44 password lenses (scrypt key, frozen params), one LWW rule, and a change feed with per-subscriber replay. Nothing uses it yet. Also: mini-relay honours `limit` (newest-first) so pagination is testable; nostr-tools becomes a hard dependency; fake-indexeddb as a dev dependency. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…nly wire Phase 2 of removing Gun. HoloSphere keeps the same public API but runs on the event-sourced store: every write is a signed kind-30078 event applied locally and published by the relay transport; reads are store lookups; subscriptions ride the store's change feed (per-subscriber replay, no echo storms, no quarantine, no reconnect heartbeat, no read/write deadlines). - content/global/node/utils rewritten over the store; hologram redirect and cascade rules kept verbatim, backlinks replace the _holograms sub-nodes - signing.js is network-free: envelopes come from the store's events table; enforce reads resolve pointer claims through the instance (cycle-guarded) - relay-transport.js: store.apply ingest, persisted cursors, paginated backfill, warm since-catch-up, reconnect + reopen backoff, resync() - private (password) lenses re-implemented with NIP-44 (private-ops.js) - handshake NIP-17 only; registry/subscriptions shims over the store - new API: listHolons/listLenses/listKeys/getBacklinks/exportEvents/importEvents; removed: gun/getGun/getNodeRef/putNode/userName/configureRadisk/ getRadiskStats/getAllLegacy/rehydrate/migrateRelays/gunOptions/backend - gun dropped from package.json; ws optional; browser bundle has no node: imports - tests: testenv on the memory adapter + mini-relay; Gun-only suites deleted, the rest ported; new relay-sync suite; enforce-mode skips removed (suite green unsigned, shadow and enforce) Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…ore, no gun
core: `relays.ts` (DEFAULT_RELAYS, resolveRelays, parseSigningMode,
signingOptionsFor) replaces the backend switch; factory takes
{relays, store, signing, nostr}; relayBackup removed (the relay IS the
wire); task deletion reads backlinks through `getBacklinks`; federation
publish no longer registers raw-graph forwarders; "Gun merges" comments
corrected (put replaces the whole record).
kiosk / wequest: instance built with relays + IndexedDB store; peer/
backend env keys, Buffer polyfill and relay backup dropped; presence
treats a null emission as removal; junk-guard no longer knows Gun souls.
Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH
Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…he legacy "Show all data" overlay web: the layout builds HoloSphere through the core factory with relays, IndexedDB store, signing mode and projections — no backend switch, no raw-graph console repair helpers (__nuke / __repairCircular). Global holon discovery uses `listHolons()`. The "Show all data" toggle only ever revealed records stranded on the retired gun relay; it is gone from the title bar, lensFilters, QueryManager and the seven lens views — `_unverified` items are simply never shown. The calendar feed lambda runs on the memory adapter. Buffer polyfill and relay backup removed. holosphere: subscribeFederated loses its `includeLegacy` / `setLegacy` overlay (getAllLegacy no longer exists). Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Single factory path: HOLOSPHERE_RELAYS (default production relays), HOLOSPHERE_STORE_DIR for a warm file-backed store, HOLOSPHERE_SIGNING read mode. holon_list enumerates via `listHolons()` instead of the raw graph root; the peer URL, backend switch and radisk sleep are gone. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
… gun server, proxy and raw-graph reads The factory builds HoloSphere with HOLOSPHERE_RELAYS (default production relays), a file store under HOLOSPHERE_STORE_DIR and the read-side signing mode; the backend switch, peer override, relay backup and radisk inspection are gone. Boot awaits `ready()` instead of poking the graph root. Onboarding scenes write a member's DNA through a small `dna.js` module (`<user>/dna/<user>`) instead of raw graph puts; the deep-link handler reads through `db.get`. Schema scripts publish public global records. Deleted: GunServer, proxy, merge_gun_data, testAuth, guntest.html, gun-sync/config tests; `gun` and `http-proxy` dropped. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…e gun repair scripts Root and core scripts build HoloSphere with relays + a memory store (the registry backfill opens the bot's file store to enumerate holons) and close the instance on exit. Importers sign with the importer key and no longer need a relay backup or a scratch radata dir. find-dangling-holograms, repair-circular-holograms and holon-health-check only repaired damage specific to the gun graph and are removed. discord-ui passes relays, a file store and the signing mode. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Repo-wide grep gate passes (only CHANGELOG history and the incident notes still mention gun). Docs describe the relay-only data layer: CLAUDE/AGENTS, README, ONBOARDING, TUTORIAL, docs/architecture, docs/realtime-sync (rewritten), holosphere NOSTR-BACKEND (rewritten), SIGNING, VIBE, README. Gun-era comments across web/kiosk/core scripts/holosphere are reworded; holosphere tests drop the unused gun option helpers and ignored backend flags; radisk examples removed. deleteGlobal now calls deleteFunc (the namespace import had no `delete`). .env.example loses the peer key, CI runs on Node 22 with a HOLO_TEST_SIGNING=enforce leg, lockfile has no gun. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Standard-kind projections (NIP-52 / NIP-99 / kind 0 / NIP-51 / NIP-58 /
NIP-29) are published for every lens by default on every surface:
`parseProjectionList(undefined)` now means "all", and the new
`projectionOptionsFor({ appName, privateKey, lenses })` in
@holons/core/nostr builds the `nostr` block for kiosk, wequest, web, the
Telegram and Discord bots and mcp-ui (which now generates its own key when
none is configured so projections share the signer's pubkey).
`HOLOSPHERE_PROJECTIONS=off` opts out.
The signing-mode switch is gone: `HOLOSPHERE_SIGNING` / `VITE_*_SIGNING`,
`parseSigningMode`, `signingOptionsFor`, the factory `signing` option, the
web read-key seeding and `__signingReport`, the kiosk `resolveSigningMode`,
the Netlify/CI knobs and the enforce read-list integration test. Every
write is signed by the instance key and published to the relays; shadow /
enforce remain library-only test modes.
Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH
Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Nostr secret keys are now `*_NSEC` env vars and may hold NIP-19 `nsec1…` (64-char hex still accepted): HOLOSPHERE_NSEC, VITE_HOLOSPHERE_NSEC, ATLAS_IMPORTER_NSEC. holosphere normalizes the key at every entry point (constructor, login, enableSigning, nostr-events) and @holons/core/nostr gains nsecToHex / toNsec / generateNsec, which the bots, mcp-ui, web HNS and the scripts use instead of assuming hex. The Ethereum deploy key is unmistakably separate: ETH_PRIVATE_KEY, ETH_RPC_URL, ETH_CHAIN_ID, ETH_NETWORK (was WEB3KEY / WEB3PROVIDER / CHAINID / NETWORK). Env examples and docs updated. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
VITE_HOLOSPHERE_NSEC is gone. The web HNS registry id comes from VITE_HOLOSPHERE_NPUB (npub1… or hex) via the new npubToHex / toNpub helpers in @holons/core/nostr; HOLOSPHERE_NSEC stays server-side. The key generator prints both halves with the right env names, and the Netlify secrets note now states the rule: no secret ever carries a VITE_ prefix. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…ram / discord / tooling) Sections now follow who reads each variable, with the browser-only (VITE_) block explicitly public. Adds the keys that were read but never documented (OPENAI, DISCORD_*, MINIAPP_SHORT_NAME, DASHBOARD_ADDRESS, VITE_KIOSK_MINIAPP, VITE_VOICE_MODE, VITE_QR_BASE_URL) and fixes the dev auto-login names to the VITE_DEV_TELEGRAM_USER_* the code reads. Claude-Session: https://claude.ai/code/session_01WbVfqjbrT8288kHxxZmKuH Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…screen board when idle Tab strip - Drag a tab to reorder it (mouse: on move; touch: hold, then move so a swipe still scrolls). Order persists per device (kiosk_tab_order); hidden tabs keep their slot (`$lib/taborder`, with tests). - Hold puts the strip in edit mode: every tab wears a ✕ that hides it (never the last one). A "+" sticks to the right edge and lists the hidden tabs; picking Status passes through its framing modal, now a shared StatusConfirm component used by Settings too. - Names show while they fit, glyphs alone when they don't, and the strip scrolls sideways when even the glyphs overflow; the active tab is kept in view. Replaces the fixed 560px icon-only rule. - Pinning stays on the active tab's pin button. Settings - Every tab switch writes through one `setTabShown`, the same path the strip's + and ✕ use, so the two surfaces never disagree. - New Tasks and Calendar switches (on by default) so any tab can go. - The Shifts sub-line no longer names the Elinor format. Board - When the chrome retreats (idle) the card and the page surface close their frames and the board runs edge to edge; any touch brings the frames back on the header's timing. - Sky, frame, header padding and corner radii scale with the viewport (clamped), so a phone keeps only a sliver of frame. Dock - The "+" accepts an id, a registered label, an npub, an Ethereum address, a pasted link or a bare name (parseHolonAdd); board → hub wording in the dock and settings copy. Claude-Session: https://claude.ai/code/session_01Xv1XrPrRvcrsUuLeVrH797 Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…he relay-only architecture - ONBOARDING.md: the relays are the wire; local store per surface; every app and package in the repo map; single root .env; branch from `dev`. - README.md: architecture diagram and packages table cover web, kiosk, wequest, telegram, discord, text, ai, mcp, voice and holosphere; quick start uses the root env template; docs list links the Nostr docs. - CONTRIBUTING.md / TUTORIAL.md: root .env, `dev` as the target branch, jest + testenv for holosphere changes, rebuild core before running apps. - docs/nostr-onboarding.md: rewritten for the current state — store tables, LWW rule, cursor/backfill behaviour from relay-transport.js, projections on by default, nsec key naming, kiosk data path. - apps/kiosk: README lists the eight tabs, the editable strip and dock, the real data path and dev commands; .env.example documents VITE_KIOSK_PROJECTIONS; docs/README.md indexes the onboarding. Claude-Session: https://claude.ai/code/session_01XTZRBNzjaUKXmXskxst6tn Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
… config The relay is the only wire; these were the dead leftovers still naming Gun: - holosphere/spike: the four scripts still imported 'gun' (no longer a dependency) and wrote to sphere.gun. Rewritten over two HoloSphere instances sharing a relay — publish, lose the cache, rehydrate — and the README describes the relay-only flow. - holosphere/examples: the Svelte example called the removed radisk API; htmldemo loaded a gun-era CDN build (holosphere@1.1.14). Both now use the v2 config / the local rebuilt bundle. Landing copy no longer says "Built on GunDB". - telegram-ui: log strings said "GunDB via HoloSphere 1.3"; the unused dbPath getter defaulted to ./radata; radata/** ignore patterns dropped from eslint, jest and vitest configs. - comments in Map/VideoCall and two holosphere tests (waitForGun → waitForStore); CLAUDE.md, AGENTS.md and the PR template now warn about holosphere-store/ instead of radata/. Claude-Session: https://claude.ai/code/session_01A4KAj3YieXCgn3ipdRzdpy Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…l federation The dock gains an earth under its sky. DockMap projects each board's claimed hexagon into field pixels (projectBoards), answers what cell a point falls on (cellAt), and the orbs hover above their places tied down by beacon cones. Dragging an orb onto a bare hexagon asks to make that cell its home, and HexPicker now announces a claim so the beacon lands at once rather than on the next look-up. FederationLens becomes a flow editor for one pair: two arrows per lens, one each way, each carrying its source holon's colour, so direction is drawn rather than spelled. Opening it federates nothing — the record is written when the first flow opens and removed when the last one closes. Three fixes to how the dock behaves: Every circle was named after whichever board was open before it. The layout wrote the dock entry from two independent stores, and the holon id changes a tick before the async bind clears the name, so a new board was stamped with the previous one's. Holons that publish no name of their own then kept that borrowed name forever, since an empty name was never allowed to clobber a learned one. The name is now written only where it is resolved, by the code that knows which holon it resolved for, and an unresolved holon falls back to its registered label or its bare id (renameEntry/nameBoard). The dock re-resolves every circle while it is up, which also heals entries already stored wrong. Dropping a federated orb on a hexagon always came out a federation. The drop scan captured any orb within a full diameter of the pointer, but linkOverlap tows a federated partner along within 1.9 radii of the carried orb for the whole drag — so the partner matched on every frame and the hexagon only lit between them. Capture now requires the pointer to be inside the target orb, one radius, matching what taps and the intersection lens already used (orbUnder). Orbs piled onto a single pixel at the edges. The pull toward a place grows with the distance to it, so a place far outside the view slams its orb into the boundary with a force no repulsion can answer, and several such places stacked their orbs into one of which only the top was tappable. Anchors are clamped into the field first, which makes that pull local and weak enough for repulsion to spread the parked orbs along the edge. The boundary margin is also capped at a third of the field, not a half, which collapsed every orb onto one line on a field narrower than 2.6 radii. Six specs added for the new pure helpers, each checked to fail without its fix. Claude-Session: https://claude.ai/code/session_01A4KAj3YieXCgn3ipdRzdpy Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…license The agreement no longer carries the "unexecuted template — have it reviewed, then sign" banner, and README loses the matching "Status & how to use" section covering signature, counsel review, registration and the assignment upgrade path. What stays is the chain-of-title table itself. Also drops the parenthetical "(formerly Harvest)" from the recitals: the project has one name. Claude-Session: https://claude.ai/code/session_01A4KAj3YieXCgn3ipdRzdpy Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Both subdomain labels resolved to -5459621960. They now resolve to -1003691108237, so commons.hubs.network and valley.hubs.network open the new holon, as does the /commons and /valley path on any host. docs/nostr-onboarding.md carried the old id as its running Commons Hub example throughout — the event sample, the filter examples, the nak and strfry commands, the vocabulary entry and the subdomain table — so it moves with it. Test fixtures in @holons/core keep the old string: there it is an arbitrary group id, not a route. Claude-Session: https://claude.ai/code/session_01A4KAj3YieXCgn3ipdRzdpy Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
… sync gates Groundwork for making a lens able to declare a standard Nostr kind as its canonical wire. Four fixes that stand on their own, none of which changes observable behaviour while 30078 is the only kind in play. The envelope index was keyed by pubkey alone, so one author's two encodings of the same record — the kind-30078 envelope and a standard kind — collided in one slot even though NIP-33 makes them independent replaceable slots on the relay. The first standard write by our own key would evict the legacy envelope. The record survived, which is what made it easy to miss: getEvents, exportEvents, nextCreatedAt and enforce mode all read envelopes, not records. Keyed by author and kind now, in both the apply path and the snapshot-hydration path. Missing the second would have dropped standard events from the index on every restart, silently, and left nextCreatedAt emitting colliding timestamps. exportEvents narrowed by reading `h` and `l` straight off the event. Those tags are the 30078 envelope's way of carrying the address; another encoding carries it elsewhere, so a narrowed export would have returned nothing at all for such a lens. It decodes instead, and only when a narrowing argument is given. The paginated backfill counted "events the store accepted" as pagination progress. That conflated two unrelated things: an event of a kind this store does not consume, and an author's superseded claim, which `_storeEvent` drops so it never enters the events table. Neither is ever reported as seen, so both read as progress on every page and the walk could only end at the page cap or the end of history. It now counts ids this run has not walked, so `fresh === 0` means what the loop needs to know: the page was entirely a re-fetch. The live subscription advanced the sync cursor on a deny-list of three reasons, which let every reason nobody had thought of yet advance it and silently skip that history on the next catch-up. Inverted to an allow-list. Verified: the holosphere suite passes in all three signing modes, default, shadow and enforce. The two claim-keying tests fail against the old code. Claude-Session: https://claude.ai/code/session_01A4KAj3YieXCgn3ipdRzdpy Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
The store took exactly one event kind and read a record's address straight off the envelope's `h` and `l` tags. Both are now per-kind decisions, held in a small registry: which kinds this store consumes, and how each one decodes into an address. That is what a lens needs before it can name a standard Nostr kind as its canonical encoding instead of the kind-30078 envelope. A store nobody configured is unchanged. The registry is seeded with the legacy envelope alone, and a codec cannot claim kind 30078 even if it asks to, so the envelope grammar can never be hijacked by a lens. `decode` returns an array rather than one claim, because an event may name several addresses at once — a NIP-09 kind 5 retracts every coordinate in its `a` tags. So `apply` now runs the address work per claim, reports `applied` when at least one landed, and returns them all as `records`. Its `record` stays the first, which is what every existing caller reads. The registry is mutable because `enableSigning` can register projections long after the store was built. A codec's grammar is untrusted input, so a decoder that throws makes the event malformed rather than taking the store down. Snapshot hydration decodes through the same registry as `apply`. Reading the envelope tags directly there would have dropped every standard-kind event from the claim index on restart, silently, with the records still present and `nextCreatedAt` handing out timestamps that collide on the relay. Nothing declares a standard wire yet; this is the mechanism only. The relay filter and the signing layer's envelope decode are still envelope-shaped and come next. Verified in all three signing modes: default, shadow and enforce. Claude-Session: https://claude.ai/code/session_01A4KAj3YieXCgn3ipdRzdpy Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Completes the plumbing a standard-primary lens needs. The store could already consume more than one kind; now the relay layer can fetch them and the signing layer can read them. `filterFor` becomes `filtersFor`, returning one REQ shape per wire. The legacy envelope is always first and always carries the wire id '30078', which is what keeps every cursor already on disk resolving to the same key. A lens whose codec claims a standard kind contributes its own shapes after it, and each gets its own subscription, its own reopen backoff and its own cursor. Separate cursors are the point, not an implementation detail. Two wires on one lens have unrelated timestamp distributions: a lens frozen on the envelope while its standard kind advances would have a shared cursor dragged past the envelope's own tail, silently skipping a straggler from a peer still running old code. It also means a lens long warm on the envelope BACKFILLS a wire it has never carried, rather than catching up from a `since` that belongs to a different stream — which is how a peer's older history gets found at all. The new suite covers exactly that and fails against a shared cursor. The signing layer decoded envelopes with `eventToItem`, which reads the event's content as the item. That is only true of the envelope. A lens carried on a standard kind keeps its fields elsewhere, so every item on such a lens would have read as unaccounted: pending under enforce, a mismatch under shadow. Four call sites now decode through the store's wire and ask what an event claims AT one address, which also handles an event claiming several. The fifth is left alone on purpose and says so: it only ever folds the internal membership log, which can never be carried on a standard kind. A HoloSphere can now be handed a wire registry as `store.wire`. Omitted, it gets the envelope alone, so nothing that exists today changes. Verified in enforce mode: 54 suites, 290 passing. Claude-Session: https://claude.ai/code/session_01A4KAj3YieXCgn3ipdRzdpy Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
The pilot for standard-primary lenses, and the first one that pays off: a
Holons surface can now read Elinor's schedule with an ordinary lens call.
Verified against the live relay — `getAll('-1003691108237', 'shifts')` returns
105 occurrences and 56 signups, with holders resolved per occurrence.
Nothing is asked of Elinor. These are the NIP-52 events it already publishes;
this decodes them into records at ordinary lens addresses. An occurrence lands
on `shifts` at `<date>-<code>`, and each signup lands on `shifts_rsvp` under
its own address rather than being merged into the occurrence, which would
re-enter the write path and the projection ratchet reverse sync exists to
avoid.
Read-only on the wire, deliberately. Occurrences are the coordinator's to
publish, and a signup carries a person-level rule — newest across a person's
linked keys — that the generic per-address write path would get wrong. So there
is no encoder, and signups keep going out through `buildRsvpTemplate`.
A signup's occurrence is read from its `a` address rather than its own `d` tag.
That is not defensive coding: Elinor appends the subject's key prefix to an
on-behalf-of request, a grammar the shift d-tag rule does not admit, so reading
the d tag would drop those events entirely. Real fixtures pulled off the relay
cover it.
Also honours the marker divergence found earlier. Elinor treats a `p` tag
marked `on-behalf-of` as a request an agent published for someone, not
occupancy, because the real signup follows under the member's own key. These
records are marked and never counted, so a request no longer surfaces as a
phantom participant attributed to the agent.
Authorization matters more here than on the envelope: a lens carried on a
standard kind is permissionless BY KIND, and anyone can publish a 31923 with a
group tag. Occurrences are the coordinator's alone when one is pinned, and a
signup's signer is always its own subject.
Claude-Session: https://claude.ai/code/session_01A4KAj3YieXCgn3ipdRzdpy
Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
… surfaces
mcp-ui: federation_link_add / federation_link_remove return the
{ self, partner } outcome with a plain note; new federation_requests
(pending requests, optional per-lens link state with a partner) and
federation_request_answer (admin-gated accept / decline).
discord-ui and wequest say when the partner's admins were asked, and
when this key may not change the holon's federation.
Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Each taskchat message is published as a kind-9 group message whose h tag is a stable per-task group id derived from holon and task key. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
taskRef resolves holograms and federated copies to the source task so everyone shares one chat and one board. Chat messages live in the source holon's taskchat lens (members post, private tasks have none); boardUrl is an excalidraw.com live room minted once and validated strictly. Quest.canvasId is retired. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
The Kudos track counted quest:initiated and quest:completed as kudos, so creating a task showed up as kudos received from no one (ledger) or given to the holon (chord). Kudos are now only appreciation:sent — one person thanking another. The ledger events are untouched; scoring still reads them. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
The people pickers (edit form hosts/participants, the card's Add someone, the completion dialog) still list the holon's members at once; a last entry, "Search the federation…", opens an inline search over the people of the holon's direct federation partners. Core: @holons/core/holons loadFederationPeople reads the partners' users rosters folded with the holon's own (linked keys recognised, members left out) and findPeople matches by name, @username or id. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…hows them A Telegram file_id opens only with the token of the bot that received it, so photos sent to a group run by another bot (Lunation 83/84) showed nowhere. - core `@holons/core/media`: uploadBlob (BUD-02 PUT + BUD-11 kind-24242 auth scoped to blob and server), resolveMediaServers, and pictureUrl as the one rendering rule (replaces kiosk lib/image.ts and web utils/imageServer.ts; raw base64 is now classified before paths) - bot: hostTelegramPicture stores a hosted URL for quests, events, expenses and tags, keeping the file_id when the upload fails - web: task pictures upload with the member's key, data URI as fallback - nostr: only http(s) pictures go in `image` tags - BOT_TOKENS lets /api/image open old file_ids from the other bots - deploy/blossom: docker-compose + config for blossom.holons.io - scripts/rehost-pictures.mjs: copy old file_id pictures (dry-run default) Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
… equation A mutual-credit entry paid into the holon itself (splitWith = [holonId]) is a contribution: it adds to the giver's score in full and takes nothing from the other members. Money already worked that way; hours did not — the scorer read hours only from task time logs and skipped the hour balance, so hours declared in mutual credit scored nothing. A member's hours are now the task-logged hours plus the hour balance, less the hour expenses a task completion writes (tagged fromTimeTracking in the REA context, older ones recognised by their <task>_time_<user> id), so completed-task hours still count once. REAAggregator's calculateUserScore goes through the same rule and no longer counts those hours twice. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
An "Into {holon}" choice beside the people an expense is split with
records it as given to the holon: it counts toward the giver's share of
the fund and nobody owes it back. Such records read "{name} put into
{holon}" and "Counts toward your share" for the giver.
Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
The length buttons used lb_n_<minutes>, which the scheduler's unanchored /n_(.+)/ answered first, so picking a length did nothing. They are lb_l_<minutes> now, and a test checks that no other module's action pattern matches a booking button. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…rote A removal dated before the key spoke falls back to the earlier word; one dated after keeps it; a removed key that writes again replaces its own accepted claim and the record falls back past it. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…rafts where you look Android fires contextmenu on a touch long-press and the menu it pops cancels the pointer underneath, so every hold gesture on the kiosk died at ~500 ms: lifting a drawer chip onto a day never scheduled the task, holding an empty slot never drew an event, holding a note never reordered it. The layout now swallows the menu for touch/pen presses outside text fields (holdmenu.ts decides; a mouse right-click keeps it). The calendar's + always drafted the event at "now", so pressed over another day it wrote into today, out of sight and not in the drawer (dated). It now drafts in the window on screen — today while shown, else the window's first day at the present time — and is labelled "New event": drawn on the calendar means an event, the Tasks wall's + stays a task. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
telegram.holons.io resolves a file_id with this bot's token first, then each token in BOT_TOKENS, so file_ids from groups run by another bot (@HubsNetwork_bot) open too. The web and kiosk /api/image fall back to its /getfile after their own tokens and the peer proxy. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
iOS Safari ignores the viewport meta's user-scalable=no and a trackpad pinch arrives as ctrl+wheel. lib/nozoom.ts cancels Safari's gesture events, multi-finger touchmove and ctrl+wheel; html gets touch-action pan-x pan-y. The timeline and task graph keep their own content zoom. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
The year line takes an optional range [from, to) and title; the calendar adds a second line spanning new moon to new moon (core lunationAt), with its moons pinned to the edges and month/solar markers clipped to the span. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
… deep link New ikigai and linkedin scenes (in the default and /onboarding sequences and the welcome Change menu). ?start=onboarding runs the whole journey for new people and only the public-profile steps for people the bot knows. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…it works The welcome text no longer repeats the board URL that its button already carries. A new button row opens the dashboard's settings tab and posts the /help guide in the chat, on /start, /id, group joins and chat links. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…s API Location is no longer asked or stored (scene, both sequences, Change menu, DNA field). The onboarding Q&A, the question picker and /summarize move off the shut-down Assistants API onto the Responses API: the conversation continues by previous_response_id, the agent answers from the /help guide, OPENAI_MODEL overrides gpt-4o, and the question picker falls back to the questionnaire order when the AI fails. ontology() had no callers and is gone. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…alogue The picker reads data/values.json (125 values) instead of eight hard-coded ones: ten per page in two columns with a page counter, and each value's meaning listed in the message. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
…for speech only @holons/core/ai holds the client, model (HOLONS_AI_MODEL, default claude-opus-5-5), effort, structured JSON output, the refusal fallback and one AIError with an HTTP status and a public message. - web/kiosk breakdown and kiosk drafting use structured output (BREAKDOWN_SCHEMA, buildDraftSchema) server-side only; the browser OpenAI path is gone - the bot's onboarding agent, question picker, /summarize and photo reading run on Claude; unused OpenAI helpers deleted - ai-ui AnthropicProvider and voice-ui go through core complete() - speech (kiosk voice relay, voice-ui STT/TTS) reads OPENAI_API_KEY only; every VITE_*_API_KEY is removed - holosphere's dead OpenAI summarize path removed - @anthropic-ai/sdk 1.1, kept SSR-external in kiosk and web Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Both manifests (static + per-hub) locked the installed app to portrait; orientation is now "any" so a wall-mounted tablet opens in landscape. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
The service worker fell back to /index.html offline, which never exists (ssr off, nothing prerendered) — an offline launch got a bare 503. It now fetches and caches the app shell at install and serves it to navigations when the network is gone. Manifest: display_override (standalone where fullscreen isn't offered), lang, categories. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Orbs shrink from 6.5rem to 4.75rem. Picking a hub that is already docked opens its board instead of flying the earth to it. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
A hub's ground was as wide as its farthest member, so one member drawn off by a second hub widened the whole ground, pushed everyone else out, stretched their hubs in turn — 60 people settled ~400 radii apart. The ground (and the hub-apart distance) is now capped at what the members need packed: 60 people settle in ~27 radii, 150 in ~44, stable live. The view may shrink to 0.2 (was 0.4): a full dock on a phone needs ~0.25, and small orbs beat people off screen. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Every edit form writes as you go: a burst of edits is one put after a short quiet spell, closing the form (Done, ✕, backdrop, Esc, another card) writes whatever is still waiting, and writes go out in order. One helper ($lib/autosave) with a spec; the state is a pill in the sheet's corner beside ✕ (SaveState) — "Saving…", "Saved", or a failed write that is itself the retry button. Covered: task/event and library item (DetailModal), role, stock item, offer, shift plan, and the Flows split. Kept explicit: creating a new item (it needs its title first), the Flows rules (a signed log entry), and the Pot panel (on chain). The task form is regrouped into what / when / who / where; kind and cadence collapse to one line until there is a date; the edit switch no longer inherits the view's uppercase `.kind` badge. Library item: description first, category and value side by side. Shift plan copy no longer asks for a Save that is gone. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
parseWebApplication validates the record liminalvillage.com/apply sends (64 KB cap, unknown keys dropped, list/text types per data key). storeApplication keeps it in the holon's applications lens only after ensureApplicationsSealed has locked the lens (audience: the holon) and holds its key; otherwise it throws ApplicationsNotSealedError and writes nothing. A repeated id keeps the stored record. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
booking.json now asks the liminalvillage.com/apply questions in its order:
role is a multi choice, a visit question gates the travel questions,
transport and accommodation are optional, referral/x/linkedin/website are
new, and social links are asked only when x, LinkedIn and website are all
empty. BookingSystem honours a declarative showIf (in, notIn, allEmpty, or
a list of them) wherever it advances; skipped fields are neither asked nor
stored. Select answers are stored as option values (string or list, with
'other') and Other's text in <key>_other, instead of { value, customValue }
objects and 'other:<text>' entries.
Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Bearer APPLICATIONS_TOKEN (constant-time; unset refuses everything), body validated by @holons/core/applications, stored in APPLICATIONS_HOLON (default Liminal Village) lens applications under the web id. 503 rather than a plaintext write when the lens cannot be sealed. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
Applicants can let their ikigai and LinkedIn go on their public profile (public_profile: yes). Web applicants claim it with a t.me/HolonsBot ?start=profile_<claim> link, which writes just those two answers to the DNA record in their own holon; /apply in Telegram does it directly. Signed-off-by: Roberto Valenti <robbyvalenti@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings HolonsBot's application in line with the new web form at liminalvillage.com/apply, and gives web applications a sealed home in Holosphere.
What changes
parseWebApplication), sealed-only storage (ensureApplicationsSealed: the lens is locked with@holons/core/privacybefore any write; nothing is ever written in plaintext), and the public-profile consent (PROFILE_KEYS, profile claims).data/booking.jsonasks the same questions as the web form (multi-role, in-person / unsure / remote, referral, X / LinkedIn / website, optional transport and accommodation).BookingSystemgains a declarativeshowIf({field, in},{field, notIn},{allEmpty}), so remote applicants skip the travel questions. Select answers are now stored as values plus<key>_othertext (previously{value, customValue}/other:<text>).POST /applications(bearerAPPLICATIONS_TOKEN) stores web applications inAPPLICATIONS_HOLON(default Liminal Village,-1003864542239), lensapplications, sealed to the holon's members. 503 if the token is unset or the lens can't be sealed; idempotent per id.t.me/HolonsBot?start=profile_<claim>for web applicants.Deploy notes
APPLICATIONS_TOKENon the bot (the same value is already set on the liminalsite Netlify project) and optionallyAPPLICATIONS_HOLON.pnpm -F @holons/core buildbefore restarting: the bot imports core fromdist/./lock applicationsin the Liminal Village group.Tests
pnpm buildis needed before the full gate./applystill saves to the applicant's personal holon unsealed (unchanged); only web applications are sealed.