Skip to content

Sense 63 — The Satellites: connected windows

Sense 63 · Steady · Last touched 2026-08-20

  • last_verified: 2026-07-27

Synced from docs/design/sense_63_the_satellites.md in the engine repo — that’s the source; this page is a build-time mirror.

Status: agreed; ALL FOUR LEVELS SHIPPED (L1+L2 2026-07-17, L3+L4 2026-07-18). Owner: Miss EDA (sister-7). Named by Mig. Sequel to The Selection (the bus inside one window) and The Diagram (the instruments on it). Doc-first — no code until this is agreed.

The Sense: an JinDesk window is not the workspace — the constellation of windows is. Windows link (never “share”): each declares, per channel, what it sends and what it receives, and chrome-less Satellite windows orbit a master so that plots become free-floating linked instruments — DataDesk’s desk, reborn across screens.

Two browser windows on the same tenant link — not “share” — so that what happens in one (a brush, a navigation) happens in the other, live, with each window choosing what it sends and what it receives.

The founding scenario: window A on page A, window B on page B, both showing the same tenant. Link them on the Selection channel, and from then on one brush scopes both worlds. Unlink, and each keeps its own. And the inverse scenario is just as legitimate: two windows linked on the page but not the selection — navigate in A, B follows to the same page, but each window holds its own cohort. Side-by-side comparison of two selections over an identical view, with one hand steering both.

This is the DataDesk inheritance completing itself: there, every plot was a little window of its own, and all of them answered the same brush. We have the brush (the bus). This gives the bus a reach beyond the window.

TermMeaning
LinkThe relationship. Windows are linked, never “shared” — a link is directional, per-channel, and revocable. Connection, not copying.
ChannelOne dimension of linkable state. First two: selection, page. The set grows (candidates below).
Send / ReceivePer-window, per-channel declarations. A window may send a channel, receive it, both, or neither. Observer pattern on the receive side, sender pattern on the send side — the two are independent switches.
The HailThe always-on control plane. Every window always listens for control messages (roll call, “everybody listen”, state requests) — even when it receives no content channel. Content is opt-in; being addressable is not.
SatelliteA chrome-less auxiliary window (no sidebar, no nav) opened from a master window, pre-linked to it. Working name — see “Naming” below.

Transport (L1: one browser) — the SatelliteChannel

Section titled “Transport (L1: one browser) — the SatelliteChannel”

The wire’s jinflow name is the SatelliteChannel — named for what it is for, per the DDD ruling “foreign names never surface” (docs/ddd, 2026-07-17): implementation vocabulary stays behind a jinflow-speaking boundary, exactly as Bronze wraps SAP’s column names. L1 implements it over the browser’s native same-origin BroadcastChannel — a name that must not surface (it near-collides with Sense 55, The Broadcast); L3 re-implements the same SatelliteChannel over SSE without any vocabulary changing. One channel per tenant:

jinflow:link:<pack>.<tenant>

Tenant scoping is structural: messages physically cannot cross tenants, and a window on another tenant never hears them. No server, no polling, no storage.

{
v: 1, // protocol version
origin: string, // this window's id (crypto.randomUUID at boot)
ts: number, // ordering; last-writer-wins on conflict
plane: 'hail' | 'content',
channel?: 'selection' | 'page' | …, // content only
verb: string, // see below
payload?: unknown,
}

Echo-guard: a window ignores every message whose origin is its own id.

The Hail (control) — always on. Every jinflow window joins the tenant channel at boot and processes control verbs regardless of its link settings:

VerbEffect
hail:whoRoll call. Every window responds with its id, page, link matrix, and whether it’s a satellite. Powers a “linked windows” HUD — you can see the constellation.
hail:listen <channel>The “everybody listen” command: asks all windows to switch on receive for a channel. A window may decline (a policy toggle, “ignore hails”), but the default is to obey — this is Mig’s one-gesture “link the room”.
hail:state? <channel>Late-joiner handshake: who holds current state? Senders respond with their latest; the joiner adopts the newest ts.
hail:blinkThe attention pulse (see “Attention” in The Diagram): every receiving window blinks the live Selection — asymmetric, ≤ 800 ms, reduced-motion-safe. Fired automatically when a Selection arrives over the Link, or explicitly by a sender; in L3 presenter mode this is the presenter’s laser pointer on every viewer’s screen.

Content — opt-in per window, per channel, per direction. The link matrix:

send receive
selection [x] [x]
page [ ] [x]

Each window owns its matrix (a small popover in the header / selection HUD). The matrix is the whole policy — there is no “master” at the protocol level; masters exist only as a default configuration (see Satellites).

selection — payload is the resolved Selection: { anchor, ids }. The same shape the bus already carries and the same decision as P4a: resolved id sets travel, not predicates (DataDesk’s shared-case-ids). Receiving window writes it into its local selection store — every panel updates because they were already subscribed. Nothing on any page changes.

page — payload is the route identity: { path, params } minus the selection-bearing search params. Page and selection are separate channels by construction — that separation is what makes “same page, different cohorts” possible. Receiving window goto()s the path, keeping its own selection.

Candidates for later (each is a channel, same protocol, zero new machinery): time range / granularity, scenario, locale, active tab within a page, scroll-to-entity (“look at this row”), and runs — “what’s building right now” flowing between windows, reading sister-1’s SIS run_registry (one row per Run, reds included; shipped 2026-07-17). Channel shape to be coordinated with sister-1 when a Satellite first wants it — a run-monitor Satellite on a second screen while the master explores is the obvious debut. Convergence agreed 2026-07-17 (sister-1’s proposal, note f5aad2f7): her run_registry + ambient SSE feed supply the data (fleet variant = a server-side change on her side); runs becomes the third content channel and the chrome-less run-constellation instrument is the L3 debut piece. On the L3 planning table.

The net has many senders (Mig, 2026-07-17 — correcting a hub that had crept into the first draft): there is no “cockpit-master” role. ANY window with send: runs on may broadcast; run summaries are idempotent state keyed by run_id (last-writer-wins by ts), so overlapping senders converge and a vanished sender leaves no hole. Direct SSE subscription remains every window’s sovereign right — the channel is a courtesy relay, never a dependency; the one-connection economy is emergent, not structural. The coupling to the runs system stays payload-thin: sister-1’s feed does not know Satellites exist; Satellites do not know SSE exists.

A satellite is a window opened from a master with:

  • no sidebar. Period. (Mig, 2026-07-17.) And the topline goes with it — no persistent identity badges, credentials chrome, bell, or nav bars. A Satellite is pure glass: the instrument fills the window. Anything institutional is on demand — the corner chip (the LinkHud, which a Satellite always carries) doubles as the reveal for identity/session info when asked. Implementation latitude is explicit here (“or whatever”): the principle is nothing persistent that isn’t the instrument; the exact on-demand gesture may evolve. Note what this is NOT: hiding chrome is display, not authorization — capabilities stay server-enforced; a Satellite is exactly as signed-in as its browser profile. The flag (?frame=satellite) is honoured by the layout; nothing baked into pages;
  • pre-linked: receives page + selection, sends selection (you can brush in a satellite and the master follows) — defaults, editable like any window’s matrix;
  • opened by three affordances:
    1. “Open as window” on any panel — the panel detaches into a satellite showing just itself. This is DataDesk literally: every plot its own little window, all answering the same brush. A second screen becomes a wall of linked instruments.

    2. “New linked window” on a page — the whole page, chrome-less.

    3. The Launch (Mig, 2026-07-17): a dedicated CLI verb —

      Terminal window
      jin satellite <pack.tenant> [page] [--recv selection,page] [--send selection]

      opens the default browser as a Satellite (chrome-less, pre-linked with the given matrix; defaults as above). The terminal is a jinflow home: launching instruments belongs in the verb namespace like jin explore does. A scripted jin satellite × N is how a wall of instruments boots in one command.

A Satellite can be a sender. (Mig, 2026-07-17 — and the modal verb is the principle.) Capability, not state: a Satellite is a full protocol peer, able to send any channel — but whether it actually sends is decided by its matrix, like every other window. Wired as its owner chooses, a Satellite brushes and the master (and every other receiver) follows; it can even send page and steer the constellation’s navigation. Equally legitimately, its send switches stay off and it is a pure observer. “Master” describes the opening gesture and the pre-filled matrix, never a privilege. What no implementation may do is remove the capability — a receive-only Satellite by policy is fine; a receive-only Satellite by construction is forbidden.

The master/satellite distinction is only the opening gesture + defaults. A satellite is a full citizen; close the master and the satellite survives, merely unlinked.

Joining is self-service. (Mig, 2026-07-17.) A normal window — full chrome, opened by hand, linked to nothing — can promote itself into the current Constellation at any time: open the link matrix, switch on what it wants to send and receive, done. No invitation, no master’s consent, no difference on the wire between a born-linked Satellite and a self-promoted window. The Constellation is defined by who is linked, not by how anyone arrived. (The Hail makes self-promotion discoverable: hail:who shows a joining window what already exists; hail:state? hands it the current selection on arrival.)

Mig named the Sense The Satellites — the individual chrome-less window is a Satellite, and the ensemble of linked windows is the Constellation (the hail:who roll call literally draws it). The relationship keeps its own verb: windows link — linking, not sharing. Rejected candidates for the record: the Wing (architectural, fits Studio/Salon but says nothing about motion-following), the Mirror (wrong — implies full replication, but a Satellite may hold its own selection), the Loupe (taken by Diff Loupe).

PhaseScopeTransport
L1selection channel + link matrix UI (LinkHud) + hail (who / listen / state / blink) — SHIPPED 2026-07-17 (fb31dd24)BroadcastChannel
L2page channel + Satellite windows (detach-panel, chrome-less flag, jin satellite) — SHIPPED 2026-07-17 (8ef9729a+73892832+98652c4f)SatelliteChannel
L3Same envelope over the server leg — SHIPPED 2026-07-18, spec belowSatelliteChannel (SSE leg)
L4The Desk — named constellations in the SIS — SHIPPED 2026-07-18SIS

L3 — the SatelliteChannel’s server leg

Section titled “L3 — the SatelliteChannel’s server leg”

Approved by Mig 2026-07-18, including the identity rule.

The same envelope, a second transport: a per-tenant hub inside JinDesk server. Windows opt in (“server link”); the browser leg (BroadcastChannel) keeps serving same-browser windows, the server leg reaches other browsers and other machines — and both carry identical envelopes, so no vocabulary or protocol changes. This is what makes presenter mode real: you brush, every linked viewer’s JinDesk follows and blinks.

Wire: GET /[tenant]/api/link/stream (SSE, one connection per window) · POST /[tenant]/api/link/publish (one envelope per call, fanned out to every subscriber of that tenant). The hub is in-memory per server process — exactly the lifetime a live gesture deserves; nothing is stored.

Envelope additions: id (per-envelope UUID — a window bridging both legs would otherwise deliver duplicates; receivers keep a small seen-set) and identity (the sender’s principal, stamped server-side on publish — never client-claimed). The browser leg carries no identity; same browser = same person by construction.

The identity rule (Mig’s ruling): hail:listen obeys automatically when the sender’s identity is your own — your phone is still your desk. A FOREIGN identity’s hail:listen requires an explicit accept in each window (” asks this window to listen — allow?”). Declining is silent and unpunished. Content envelopes from foreign identities apply only per the matrix, exactly as always — the matrix remains the whole policy.

Authorization: subscribing and publishing require the tenant’s ordinary view capability — the Link shares pointers (ids, paths), never data; each window’s own session resolves what it may actually see. A viewer who cannot see the tenant sees nothing, linked or not.

L4.1 — the Desk restores the wiring, not the furniture (PROPOSED)

Section titled “L4.1 — the Desk restores the wiring, not the furniture (PROPOSED)”

⚠️ PROPOSED — NOT BUILT. Everything in L4.1/L4.2 below describes work that has been decided, not shipped. The Satellites themselves (L1–L4) are live; screen-aware Desks are not. (s7’s call, 2026-08-03: don’t advertise unbuilt furniture, don’t hide the shipped feature.)

Proposed by sister-3, 2026-08-02, out of Mig reading L4 back while we looked at pinning Desks to the personal room. Sense 63 is Miss EDA’s — this is a proposal into her doc, not a settled change. Nothing here is built.

The scenario that exposed it. A user opens four ordinary windows, tiles them a quarter-screen each, links them, saves a Desk. Reopening does not give the four tiled windows back. Mig’s read; the code agrees.

Capture is sound. saveDeskNow fires hailWho(), waits 350 ms so late windows answer the roll call, and stores self + every hailed peer. All four windows are recorded.

Reopen falls short in two independent ways.

  1. Geometry was never in the model. A DeskWindow is {path, satellite, send, recv} — no x, y, width or height in the type or in windows_json. The tiling is not lost on reopen; it is never captured.
  2. Ordinary windows come back as tabs. openDesk calls window.open(u, '_blank', w.satellite ? 'popup=yes,width=880,height=620,…' : ''). An empty feature string yields a tab. Only satellite: true entries get a real window, and those at a fixed 880×620 cascaded 60 px apart — nobody’s layout.

So the four-window Desk reopens as: this window navigates to the first entry, and three tabs appear.

What L4 does restore, and restores well: which page each window held, its satellite flag, and the whole link matrix — send/recv are re-encoded as URL params, so the Constellation comes back correctly wired. The Desk is a wiring diagram, not a furniture plan. That is a defensible thing for it to be; it is just not what “your desk, as you left it” promises.

DeskWindow gains optional geometry (screenX, screenY, outerWidth, outerHeight), and reopen passes explicit width/height/left/top for every entry, not only satellites. Optional because a Desk saved before this exists has none, and a window may not be able to read its own.

Three honesty points that belong in the decision, not in the implementation:

  • A window can read its own geometry; it cannot read a peer’s. Peer geometry would have to travel in the hail:who roll-call payload, which makes this a change to the L1 Hail envelope — s7’s call, not a Desk-local patch.
  • Positioning a tab is impossible. Restoring furniture means every non-first entry opens as a popup window. A Desk saved from four ordinary windows would reopen as one window plus three popups — a real behaviour change, and the closest thing to what the user drew. It should be decided, not slipped in.
  • The OS gets a vote. Browsers clamp or ignore position on some platforms, and a multi-monitor screenX can land off-screen on a laptop-only session. Reopen must clamp to the available screen rather than trusting stored numbers.

Several window.open calls from one click is classic blocker territory: the first is typically allowed, the rest blocked. Even today’s satellite path is fragile this way. Options: (i) require “allow popups for this host” and say so; (ii) reopen one window per click (“Open next window (2 of 4)”); (iii) degrade to tabs when popups are denied.

Recommendation: (i) with (iii) as fallback, and an explicit message either way. What must never happen is a half-restored Desk that looks like the whole one — the same failure the personal room’s protocol was just rewritten to eliminate (the_personal_room.md, “Saying what happened”).

Surfaced by the personal room: a pin is fundamentally an href, and a Desk is reconstituted by client-side JS (openDesk) with no route behind it. The pin spec assumed /{tenant}/desk/{desk_id}; no such route exists.

  1. Give Desks a route. /{tenant}/desk/{desk_id} loads the Desk and runs the reopen behind a click (Proposal B). Desks become addressable — pinnable, bookmarkable, and answerable to “send me your desk”.
  2. Let a pin carry an action. kind='desk' renders a button that calls openDesk directly. Cheaper, but pins stop being uniformly links and the Desk stays unaddressable.

Recommendation: 1. Addressability is worth more than the work it saves, and it is the only option that makes a Desk shareable.

  • The room, not the wiring. A Desk restores the furniture. Proposals A and B are accepted, with the costs they carry (peer geometry enters the hail:who payload; non-first entries become popups).
  • Desks get a route (Proposal C).
  • And further: the Desk is screen-aware — see L4.2, which supersedes the geometry half of Proposal A.

L4.2 — the screen-aware Desk (PROPOSED — NOT BUILT)

Section titled “L4.2 — the screen-aware Desk (PROPOSED — NOT BUILT)”

Mig, 2026-08-03: “I’d love to see the room. And I’d like to go even further — screen awareness. We should store the screen resolution. My layouts on my laptop are different than on my 32-inch.” Specced by sister-3; the Hail half is s7’s.

The requirement is sharper than scaling. Not “one layout stretched to fit” — genuinely different arrangements per screen. Four windows quartered on the 32-inch may be two windows stacked on the laptop. A layout is only meaningful relative to the screen it was authored on.

desk:
desk_id: morning-wall
name: Morning Fleet Wall
layouts:
- screen: { w: 2560, h: 1440, avail_w: 2560, avail_h: 1415, dpr: 2 }
windows:
- { path: /r.delta/fleet, satellite: false,
send: {...}, recv: {...},
x: 0, y: 0, w: 1280, h: 1415 }
- screen: { w: 1512, h: 982, avail_w: 1512, avail_h: 944, dpr: 2 }
windows: [ … a different, smaller constellation … ]

A layout is a complete constellation, not a geometry patch over a shared window list — because the window set legitimately differs per screen. The screen block is the signature; w×h identifies it, the rest informs remapping and diagnostics.

Saving on a given screen creates or replaces only that screen’s layout; every other layout survives untouched. This is the behaviour that makes the feature feel right: arranging your laptop desk must not destroy the one you built on the 32-inch.

Reopen — match, else remap, and say which

Section titled “Reopen — match, else remap, and say which”
  1. Exact signature match → restore verbatim. The common case, and the point.
  2. No match → take the nearest layout (closest aspect ratio, then area) and remap proportionally into the current avail_w/avail_h, clamping every window to the visible area.
  3. Either way, say so. A remapped desk announces itself (“laid out for 2560×1440, adapted to this screen”) — never silently misplaced windows. Same rule as everywhere else: an approximation that looks like the real thing is the failure mode worth engineering against.

Mig, 2026-08-03: “Can a desk be updated? Open a desk, adjust a window position, update?”

The store already can; the UI cannot. saveDesk is an upsert (ON CONFLICT (tenant_id, desk_id) DO UPDATE) and the endpoint accepts an explicit desk_id — but saveDeskNow never sends one. It prompts for a name and derives the id by slugifying it, so today “update” means retyping the name exactly, and both outcomes are silent: type it identically → you overwrite with no confirmation; type it differently → you get a second Desk instead. The user cannot tell which happened.

The missing piece is that nothing knows a Desk is open. openDesk navigates and spawns windows but records no identity, so there is no “this Desk” to update.

The route supplies it. /{tenant}/desk/{desk_id} is the Desk’s identity, and every window it opens carries ?desk=<id>. From that follows:

  • Update — an explicit action, sending the known desk_id, offered only when a Desk is open. Labelled with the target (“Update Morning Fleet Wall”).
  • Save as new — the current prompt-for-a-name path, and the only way to create.
  • Never a silent collision. Saving a new Desk whose name slugifies onto an existing id must say so and offer update-or-rename, rather than quietly replacing someone’s constellation.

With L4.2 this becomes the primary gesture rather than a convenience: open a Desk on the laptop, nudge a window, Update — and per the additive-save rule that replaces only this screen’s layout, leaving the 32-inch arrangement untouched.

The route and the popup problem solve each other

Section titled “The route and the popup problem solve each other”

Proposal C’s /{tenant}/desk/{desk_id} is not just addressability. Restoring furniture requires popups, and several window.open calls need a user gesture or the browser blocks all but the first. So the route renders a small pre-flight: which layout matched, how many windows will open, whether this screen is an exact match or a remap — and one button that performs the reopen inside the click. The gesture problem disappears into a UI that was worth having anyway.

The envelope’s rules (s7, 2026-08-03 — load-bearing)

Section titled “The envelope’s rules (s7, 2026-08-03 — load-bearing)”

Capture crosses into L1’s protocol, so the constraints are hers:

  1. Self-reported, never peer-read. Each window reports its OWN {x,y,w,h}
    • screen signature into hail:who; no window reads another’s. The coordinator assembles the roll call from self-reports, which keeps the envelope’s self-owned-state discipline intact.
  2. “Ids travel, data never” is not broken. That invariant guards tenant data. Window geometry and screen are UI metadata about the window itself, so they ride the wire with no auth gate. Stated explicitly so nobody reads the invariant as weakened.
  3. Same-identity only. Geometry and screen travel only among one identity’s own windows — a Desk is inherently “my windows across my screens”. They are NEVER emitted to a foreign identity: a presenter-mode viewer receives my selection, never my furniture. It is private, and useless to them besides — they cannot reposition my windows. The Constellation’s consent model stays whole: foreign identities get pointers, not my desktop.

Window geometry stays out of the selection bus. The selection envelope is {anchor, ids} — lean and high-frequency. Furniture rides the capture-time roll call, a different cadence entirely. Two envelopes, two purposes.

“Geometry” here means the furniture, not the brush. Two different things wear that word, and the collision has already confused a reader (Mig, 2026-08-03). Window geometry = a window’s {x,y,w,h} + screen — the subject of this section. A brushed rectangle or lasso is something else entirely: a spatial predicate, first-class inside a Selection ({entity, predicates[], combine}Set<id>, see The Selection). Nothing here restricts it.

What the bus carries today is the resolution, not the shape: brush a box, the ids it resolves to travel, the box does not (SelectionState = {anchor, ids}). Whether a spatial predicate should ALSO travel — so a peer on a different layer can re-resolve it, so a 500k-id selection stays one bbox on the wire, so a peer can draw where you brushed — is a live question for the Selection bus’s owners, not something L4.2 settles.

  • A window can read its own geometry, never a peer’s — hence precision 1 above: capture is self-report into the roll call, not inspection.
  • Multi-monitor is a v2. window.screen describes the current monitor; screenX/screenY can place a window on another, but the browser won’t enumerate the arrangement without the permission-gated Window Management API (getScreenDetails()). V1 captures the current screen and clamps; a true multi-screen Desk is an opt-in follow-up, not a silent half-feature.
  • The OS gets a vote. Platforms clamp or ignore requested positions. Reopen proposes; it never assumes it won.
  • Signatures are not stable forever. Changing OS display scaling changes w×h and orphans a layout into the remap path. Acceptable: it degrades to “adapted”, not to broken.
  1. Capture (own window) — geometry + screen signature on save. No protocol change; a single-window Desk already round-trips.
  2. Capture (peers) — the hail:who payload carries geometry + screen. s7’s envelope; blocks multi-window fidelity.
  3. The route/{tenant}/desk/{desk_id} + pre-flight + gesture-scoped reopen, and the ?desk= identity that makes Update possible. BUILT 2026-08-05 against today’s wiring-only restore, as sequenced. The pre-flight states plainly that it restores the WIRING, not the furniture, so the page cannot over-promise what capture has not yet delivered; blocked popups are counted and reported rather than leaving a half-restored desk looking whole. kind='desk' in the_personal_room.md is unblocked and shipped with it.
  4. Match / remap — exact-signature restore, nearest-layout remap, the announcement.
  5. Multi-monitor — Window Management API, opt-in.
  • Same tenant only. The channel name enforces it; there is no cross-tenant linking, consistent with tenant isolation.
  • Ids travel, data never. A selection message carries entity ids, not rows. Same-origin windows could read each other’s data anyway, but L3 (server transport) inherits this cleanly: the wire carries pointers, each window’s own authorized session resolves them.
  • No hidden coupling. A window that neither sends nor receives behaves exactly as today. The bus inside a window is untouched; the Link is a transport behind the existing selection store, invisible to every panel.
  • Conflict model is trivial by design: last writer wins, per channel. Two simultaneous brushes in two sending windows resolve by ts — acceptable for a human-speed gesture; no CRDTs until reality proves we need them.
  1. Naming — settled: Sense 63 The Satellites; window = Satellite, ensemble = Constellation, relationship = link.
  2. hail:listen default-obey: right for a trusted single-user desktop; should L3 (multi-person) require explicit accept per window?
  3. Should the link matrix persist across reloads (localStorage per window is shaky — windows have no stable identity across sessions) or is L4’s named link-group the only persistence?
  4. Does page follow include search params like sort/filter state (full view replication) or the bare route? Proposal: bare route + a separate view-state channel later, same reasoning as page ⊥ selection.

Numerical neighbors:Sense 61: The Strata · Sense 64 — The Docent: the tour as a declaration

jazzisnow jinflow is a jazzisnow product
v0.64.7 · built 2026-09-20 19:48 UTC