Unify window chrome: single host-owned header, browser-style route contract for all islands #35

Closed
opened 2026-04-14 16:17:53 +00:00 by timur · 3 comments
Owner

Problem

Window chrome in hero_os is fragmented:

  • Some islands render their own IslandHeader internally (Files, Editor, Calendar, Contacts, ~16 native islands).
  • Others rely on the outer .win-header bar provided by the window (src/components/window.rs:393).
  • A hardcoded list in crates/hero_os_app/src/main.rs:1973 (contacts | filesystem) opts specific islands into a "headerless + floating hover controls" style (window.rs:491-551).
  • Iframe-embedded islands (shrimp, hero, office, panel, compute, ~25 total under hero_archipelagos/archipelagos/embed/) have no header at all and rely on their embedded UI to self-identify.

Result: at least three visually distinct window styles coexist, chrome decisions are made per-island, and the hardcoded opt-in list doesn't scale.

Proposal: the browser model

Collapse chrome and content into the browser's mental model:

Browser Hero OS
Browser chrome (tab, URL bar, back) Window header (title, breadcrumbs, close/min/max)
Web page Island content
document.title / history.pushState App pushes route into shared header
<iframe src> + internal nav iframe postMessage → window header
Favicon Island icon (from metadata)

The window always owns the chrome. The app only renders content. One header component, one stylesheet, one behavior — styled as the floating rounded-card header that Files currently has, with close/min/max revealed on hover.

Contract

Native islands (Dioxus)

Receive a WindowRoute context and write to it:

let route = use_window_route();
route.set(Route {
    breadcrumbs: vec!["Documents", "Projects", "foo.md"],
    actions: rsx! { button { "Upload" } }, // optional slot
});

Iframe islands

Same contract via postMessage:

parent.postMessage({
  type: "hero:route",
  breadcrumbs: ["Inbox", "Thread #42"],
}, "*");

The window's header component listens to either source and renders: breadcrumbs + close/min/max + optional action slot.

What this deletes

  • Hardcoded headerless list in crates/hero_os_app/src/main.rs:1973
  • Headered/headerless branch in src/components/window.rs:393 and :491-551 → merged into one path
  • IslandHeader call sites inside ~16 native islands (component moves to host-owned)
  • .win-header / .win-controls CSS
  • Per-island chrome divergence (impossible by construction)

What this unlocks

  • OS-level deep linking — route reified per window, aligns with existing hero_ui_routes spec
  • Consistent drag surface — header always present, always draggable (drag handler already detects .island-header, see window.rs:323-345)
  • Window title / dock tooltip reflects deepest breadcrumb automatically
  • Back/forward per window becomes possible (route stack on WindowState)
  • Iframe ↔ native parity — shrimp, files, hero, office all behave identically

Migration order

  1. Build WindowRoute context + postMessage listener + unified header component in window.rs. Always rendered.
  2. Migrate Files: drop internal IslandHeader, write to WindowRoute. Proof-of-concept for native.
  3. Wire shrimp's postMessage emitter in /hero_agent/ui. Proof-of-concept for iframe.
  4. Sweep remaining 15 native IslandHeader callers.
  5. Delete .win-header, .win-controls, the headerless list, and internal IslandHeader call sites.

Each step is independently shippable and each one deletes more code than it adds.

Open questions (to resolve before spec)

  1. Action slot semantics — should apps be able to push arbitrary Dioxus Element into the header (native), and HTML/button-descriptor JSON via postMessage (iframe)? Or restrict to a fixed schema (icon + label + id) for parity?
  2. Route stack / back-forward — in scope for v1, or defer? Affects WindowState shape.
  3. IslandHeader component home — keep in hero_archipelagos/core/src/island_header.rs (so embed islands can still use it standalone outside the OS), or move to hero_os_app?
  4. postMessage origin handling — iframes can send any message; do we want an allowlist per island, or trust-by-frame-id?
  5. Deep-link URL format — how does window route serialize into the OS URL? Alignment with hero_ui_routes spec needed.
  6. Mobile parity — mobile window (window.rs:78) currently has a simplified header. Does it use the same WindowRoute context with a different renderer, or keep diverged?
  7. Actions before route is set — if an island hasn't pushed a route yet, header shows island name from metadata. Confirm this is the fallback.

Will post questions as comments; once resolved, spec PR follows.

## Problem Window chrome in hero_os is fragmented: - Some islands render their own `IslandHeader` internally (Files, Editor, Calendar, Contacts, ~16 native islands). - Others rely on the outer `.win-header` bar provided by the window (`src/components/window.rs:393`). - A hardcoded list in `crates/hero_os_app/src/main.rs:1973` (`contacts` | `filesystem`) opts specific islands into a "headerless + floating hover controls" style (`window.rs:491-551`). - Iframe-embedded islands (shrimp, hero, office, panel, compute, ~25 total under `hero_archipelagos/archipelagos/embed/`) have no header at all and rely on their embedded UI to self-identify. Result: at least three visually distinct window styles coexist, chrome decisions are made per-island, and the hardcoded opt-in list doesn't scale. ## Proposal: the browser model Collapse chrome and content into the browser's mental model: | Browser | Hero OS | |---|---| | Browser chrome (tab, URL bar, back) | Window header (title, breadcrumbs, close/min/max) | | Web page | Island content | | `document.title` / `history.pushState` | App pushes route into shared header | | `<iframe src>` + internal nav | iframe `postMessage` → window header | | Favicon | Island icon (from metadata) | **The window always owns the chrome. The app only renders content.** One header component, one stylesheet, one behavior — styled as the floating rounded-card header that Files currently has, with close/min/max revealed on hover. ## Contract ### Native islands (Dioxus) Receive a `WindowRoute` context and write to it: ```rust let route = use_window_route(); route.set(Route { breadcrumbs: vec!["Documents", "Projects", "foo.md"], actions: rsx! { button { "Upload" } }, // optional slot }); ``` ### Iframe islands Same contract via `postMessage`: ```js parent.postMessage({ type: "hero:route", breadcrumbs: ["Inbox", "Thread #42"], }, "*"); ``` The window's header component listens to either source and renders: breadcrumbs + close/min/max + optional action slot. ## What this deletes - Hardcoded headerless list in `crates/hero_os_app/src/main.rs:1973` - Headered/headerless branch in `src/components/window.rs:393` and `:491-551` → merged into one path - `IslandHeader` call sites inside ~16 native islands (component moves to host-owned) - `.win-header` / `.win-controls` CSS - Per-island chrome divergence (impossible by construction) ## What this unlocks - OS-level deep linking — route reified per window, aligns with existing `hero_ui_routes` spec - Consistent drag surface — header always present, always draggable (drag handler already detects `.island-header`, see `window.rs:323-345`) - Window title / dock tooltip reflects deepest breadcrumb automatically - Back/forward per window becomes possible (route stack on `WindowState`) - Iframe ↔ native parity — shrimp, files, hero, office all behave identically ## Migration order 1. Build `WindowRoute` context + `postMessage` listener + unified header component in `window.rs`. Always rendered. 2. Migrate Files: drop internal `IslandHeader`, write to `WindowRoute`. Proof-of-concept for native. 3. Wire shrimp's `postMessage` emitter in `/hero_agent/ui`. Proof-of-concept for iframe. 4. Sweep remaining 15 native `IslandHeader` callers. 5. Delete `.win-header`, `.win-controls`, the headerless list, and internal `IslandHeader` call sites. Each step is independently shippable and each one deletes more code than it adds. ## Open questions (to resolve before spec) 1. **Action slot semantics** — should apps be able to push arbitrary Dioxus `Element` into the header (native), and HTML/button-descriptor JSON via postMessage (iframe)? Or restrict to a fixed schema (icon + label + id) for parity? 2. **Route stack / back-forward** — in scope for v1, or defer? Affects `WindowState` shape. 3. **IslandHeader component home** — keep in `hero_archipelagos/core/src/island_header.rs` (so embed islands can still use it standalone outside the OS), or move to `hero_os_app`? 4. **postMessage origin handling** — iframes can send any message; do we want an allowlist per island, or trust-by-frame-id? 5. **Deep-link URL format** — how does window route serialize into the OS URL? Alignment with `hero_ui_routes` spec needed. 6. **Mobile parity** — mobile window (`window.rs:78`) currently has a simplified header. Does it use the same `WindowRoute` context with a different renderer, or keep diverged? 7. **Actions before route is set** — if an island hasn't pushed a route yet, header shows island name from metadata. Confirm this is the fallback. Will post questions as comments; once resolved, spec PR follows.
Author
Owner

1: no for now. 2: in scope for v1, but needs to feel like native app and not browser back and forward. 3: can remain for now, archipelagos is like our component lib. 4: trust, in prod we wont have iframes eventually. 5: elaborate. 6: unify, should not be diverged. 7: yes. again, to clarify, header doesnt shor routes as urls but breadcrumbs like native os apps. It is important to keep native os feel throughout.

1: no for now. 2: in scope for v1, but needs to feel like native app and not browser back and forward. 3: can remain for now, archipelagos is like our component lib. 4: trust, in prod we wont have iframes eventually. 5: elaborate. 6: unify, should not be diverged. 7: yes. again, to clarify, header doesnt shor routes as urls but breadcrumbs like native os apps. It is important to keep native os feel throughout.
Author
Owner

Spec: Unified Window Chrome + WindowRoute Contract

1. Goals

  • One header component, rendered by the window, for every island (native + iframe).
  • Apps push route state; header reflects it. Apps never render chrome.
  • Native OS feel: breadcrumbs, not URLs. No browser-style back/forward arrows in the chrome.
  • Shareable deep links via URL fragment (option B), invisible to normal users.

2. Data model

Lives in hero_os_app (window-owned):

pub struct Route {
    pub breadcrumbs: Vec<Breadcrumb>,
    pub actions: Vec<HeaderAction>,
    pub url_segment: Option<String>, // short slug for URL fragment
}

pub struct Breadcrumb {
    pub label: String,
    pub id: String,        // app-defined, passed back on click
    pub icon: Option<String>, // optional SVG string or icon id
}

pub struct HeaderAction {
    pub id: String,        // app-defined, passed back on click
    pub label: String,     // tooltip
    pub icon: String,      // required (icon-only buttons in header)
}

Fixed schema — no arbitrary Dioxus nodes, no arbitrary HTML. Parity native/iframe.

Extend WindowState:

pub struct WindowState {
    // ...existing fields...
    pub route: Route,
    pub route_stack: Vec<Route>,  // back history (max 50)
    pub route_forward: Vec<Route>, // forward history, cleared on new push
}

3. Native API (Dioxus)

Hook in hero_os_app::window_route:

pub fn use_window_route() -> WindowRouteHandle;

impl WindowRouteHandle {
    pub fn set(&self, route: Route);            // pushes to stack
    pub fn replace(&self, route: Route);        // no stack entry
    pub fn on_breadcrumb_click(&self, cb: impl Fn(String));
    pub fn on_action_click(&self, cb: impl Fn(String));
    pub fn back(&self);
    pub fn forward(&self);
}

Context provided by the window component to its child ({island_view} in window.rs). Island calls use_window_route() inside its component and writes whenever its internal state changes.

Migration for existing IslandHeader users: delete the IslandHeader { ... } call, replace with use_window_route().set(...). IslandHeader the component stays in hero_archipelagos/core for standalone embed use (outside hero_os), but no hero_os island calls it.

4. Iframe API (postMessage)

Protocol (trust-based; no origin allowlist for now):

// iframe → parent
{ type: "hero:route",
  breadcrumbs: [{ label, id, icon? }],
  actions: [{ id, label, icon }],
  url_segment?: string,
  mode: "push" | "replace" }

// parent → iframe (user clicked breadcrumb or action)
{ type: "hero:breadcrumb", id: string }
{ type: "hero:action", id: string }
{ type: "hero:back" }
{ type: "hero:forward" }

The window component attaches a message listener scoped to its iframe (event.source === iframeRef). Tiny JS helper hero-window-route.js shipped to iframe islands, wraps postMessage in an ergonomic API:

import { HeroRoute } from "/assets/hero-window-route.js";
HeroRoute.push({ breadcrumbs: [...], actions: [...] });
HeroRoute.onBreadcrumb(id => /* ... */);
HeroRoute.onAction(id => /* ... */);

5. URL serialization (option B)

Fragment format: /hero_os#w=files:Documents%2FProjects%2Ffoo.md;calendar:2026-04

  • Only open windows appear. Focused window is first.
  • Each entry is <island_id>:<url_segment>. Segment is URL-encoded.
  • If an island doesn't set url_segment, only its id appears: shrimp.
  • Route stack is NOT in URL (feels native — back/forward is per-session state).
  • On load: parse fragment → open each window → replay url_segment via from_url_segment().

Island implements (native):

trait RouteSerde {
    fn to_url_segment(&self) -> Option<String>;
    fn from_url_segment(s: &str) -> Option<Route>;
}

Iframe: iframe includes url_segment in its hero:route message; on restore, parent sends {type: "hero:restore", segment} after iframe loads.

6. Header component

Location: crates/hero_os_app/src/components/window_header.rs (new). Renders inside window.rs, always present (delete headerless branch).

Visual (adopt Files' current style):

  • Floating rounded card, absolute positioned at top of window, ~8px inset, border-radius 12px, backdrop-blur.
  • Left: island icon (from metadata) + breadcrumb chips (Name › Docs › foo.md), last chip bold, earlier chips clickable.
  • Right: app-provided action icons + window controls (min/max/close). All revealed on hover with 0.15s opacity transition. Icon is always visible (so you can identify a stack of windows at a glance).
  • Drag surface: entire header except interactive elements (already wired in window.rs:323-345 via .closest('.island-header') — keep the class name for compat).
  • Focused vs unfocused: focused = full opacity; unfocused = 60% opacity background, 40% controls.

Mobile renderer: same component, different CSS — full-width (no side insets), controls always visible (no hover on touch), breadcrumbs truncate middle (Files › … › foo.md).

7. Back/forward

  • Stack push on every set() (except replace()).
  • Max stack depth: 50. Oldest dropped silently.
  • User triggers:
    • Click non-last breadcrumb → pops stack to that level.
    • Keyboard: Cmd/Ctrl+[ back, Cmd/Ctrl+] forward. Wired in keyboard.rs as hero:keyboard-shortcut events.
    • No visible arrow buttons in chrome (native feel).
  • Cleared on window close. Not persisted across reloads (matches native app behavior).

8. Migration phases

Each phase is a separate PR, independently shippable.

Phase 1 — foundation (no visible change)

  • Add Route, Breadcrumb, HeaderAction, WindowRoute context.
  • Add route fields to WindowState.
  • Add keyboard bindings for back/forward (no-op until routes exist).

Phase 2 — unified header (visible change: Files-style chrome everywhere)

  • Build window_header.rs.
  • Replace both branches in window.rs:393-551 with single header render.
  • Delete hardcoded headerless list (main.rs:1973).
  • Delete .win-header, .win-controls CSS.
  • Islands without route set: header shows metadata name + icon only.

Phase 3 — Files proof-of-concept

  • Files drops internal IslandHeader call.
  • Files writes to WindowRoute on navigation.
  • Validate drag, back/forward, breadcrumb click.

Phase 4 — iframe protocol + shrimp proof-of-concept

  • Ship hero-window-route.js helper.
  • Wire postMessage listener in window.
  • Update /hero_agent/ui to emit hero:route.

Phase 5 — URL serialization

  • Add RouteSerde trait, implement for Files + shrimp.
  • Hash-fragment parser / writer in hero_os_app.
  • Replay on load.

Phase 6 — sweep remaining 15 native islands

  • Mechanical: delete IslandHeader internal calls, add use_window_route calls.
  • ~30 min per island.

Phase 7 — sweep remaining ~24 iframe islands

  • Add hero-window-route.js import + minimal breadcrumb push.
  • ~15 min per island.

Phase 8 — cleanup

  • Confirm no remaining callers, remove dead CSS and helpers.

9. Non-goals (deferred)

  • Browser-style arrow buttons in header.
  • Route stack in URL.
  • Origin allowlist for postMessage (iframes going away long-term).
  • Arbitrary Dioxus elements / HTML in action slot.
  • Per-window tab bar (multi-route per window).

10. Open during implementation

  • Exact icon format for HeaderAction.icon — inline SVG string, lucide icon id, or island-defined asset path? Propose lucide id with SVG fallback. Lock in Phase 2.
  • Stack depth 50 — arbitrary, tune later.

Ready for review. On approval, I open PR for Phase 1.

# Spec: Unified Window Chrome + `WindowRoute` Contract ## 1. Goals - **One** header component, rendered by the window, for every island (native + iframe). - Apps push route state; header reflects it. Apps never render chrome. - Native OS feel: breadcrumbs, not URLs. No browser-style back/forward arrows in the chrome. - Shareable deep links via URL fragment (option B), invisible to normal users. ## 2. Data model Lives in `hero_os_app` (window-owned): ```rust pub struct Route { pub breadcrumbs: Vec<Breadcrumb>, pub actions: Vec<HeaderAction>, pub url_segment: Option<String>, // short slug for URL fragment } pub struct Breadcrumb { pub label: String, pub id: String, // app-defined, passed back on click pub icon: Option<String>, // optional SVG string or icon id } pub struct HeaderAction { pub id: String, // app-defined, passed back on click pub label: String, // tooltip pub icon: String, // required (icon-only buttons in header) } ``` Fixed schema — no arbitrary Dioxus nodes, no arbitrary HTML. Parity native/iframe. Extend `WindowState`: ```rust pub struct WindowState { // ...existing fields... pub route: Route, pub route_stack: Vec<Route>, // back history (max 50) pub route_forward: Vec<Route>, // forward history, cleared on new push } ``` ## 3. Native API (Dioxus) Hook in `hero_os_app::window_route`: ```rust pub fn use_window_route() -> WindowRouteHandle; impl WindowRouteHandle { pub fn set(&self, route: Route); // pushes to stack pub fn replace(&self, route: Route); // no stack entry pub fn on_breadcrumb_click(&self, cb: impl Fn(String)); pub fn on_action_click(&self, cb: impl Fn(String)); pub fn back(&self); pub fn forward(&self); } ``` Context provided by the window component to its child (`{island_view}` in `window.rs`). Island calls `use_window_route()` inside its component and writes whenever its internal state changes. Migration for existing `IslandHeader` users: delete the `IslandHeader { ... }` call, replace with `use_window_route().set(...)`. `IslandHeader` the component stays in `hero_archipelagos/core` for standalone embed use (outside hero_os), but no hero_os island calls it. ## 4. Iframe API (postMessage) Protocol (trust-based; no origin allowlist for now): ```typescript // iframe → parent { type: "hero:route", breadcrumbs: [{ label, id, icon? }], actions: [{ id, label, icon }], url_segment?: string, mode: "push" | "replace" } // parent → iframe (user clicked breadcrumb or action) { type: "hero:breadcrumb", id: string } { type: "hero:action", id: string } { type: "hero:back" } { type: "hero:forward" } ``` The window component attaches a `message` listener scoped to its iframe (`event.source === iframeRef`). Tiny JS helper `hero-window-route.js` shipped to iframe islands, wraps `postMessage` in an ergonomic API: ```js import { HeroRoute } from "/assets/hero-window-route.js"; HeroRoute.push({ breadcrumbs: [...], actions: [...] }); HeroRoute.onBreadcrumb(id => /* ... */); HeroRoute.onAction(id => /* ... */); ``` ## 5. URL serialization (option B) Fragment format: `/hero_os#w=files:Documents%2FProjects%2Ffoo.md;calendar:2026-04` - Only open windows appear. Focused window is first. - Each entry is `<island_id>:<url_segment>`. Segment is URL-encoded. - If an island doesn't set `url_segment`, only its id appears: `shrimp`. - Route **stack** is NOT in URL (feels native — back/forward is per-session state). - On load: parse fragment → open each window → replay `url_segment` via `from_url_segment()`. Island implements (native): ```rust trait RouteSerde { fn to_url_segment(&self) -> Option<String>; fn from_url_segment(s: &str) -> Option<Route>; } ``` Iframe: iframe includes `url_segment` in its `hero:route` message; on restore, parent sends `{type: "hero:restore", segment}` after iframe loads. ## 6. Header component Location: `crates/hero_os_app/src/components/window_header.rs` (new). Renders inside `window.rs`, always present (delete `headerless` branch). Visual (adopt Files' current style): - Floating rounded card, absolute positioned at top of window, ~8px inset, border-radius 12px, backdrop-blur. - Left: island icon (from metadata) + breadcrumb chips (`Name › Docs › foo.md`), last chip bold, earlier chips clickable. - Right: app-provided action icons + window controls (min/max/close). **All revealed on hover** with 0.15s opacity transition. Icon is always visible (so you can identify a stack of windows at a glance). - Drag surface: entire header except interactive elements (already wired in `window.rs:323-345` via `.closest('.island-header')` — keep the class name for compat). - Focused vs unfocused: focused = full opacity; unfocused = 60% opacity background, 40% controls. Mobile renderer: same component, different CSS — full-width (no side insets), controls always visible (no hover on touch), breadcrumbs truncate middle (`Files › … › foo.md`). ## 7. Back/forward - Stack push on every `set()` (except `replace()`). - Max stack depth: 50. Oldest dropped silently. - User triggers: - Click non-last breadcrumb → pops stack to that level. - Keyboard: `Cmd/Ctrl+[` back, `Cmd/Ctrl+]` forward. Wired in `keyboard.rs` as `hero:keyboard-shortcut` events. - No visible arrow buttons in chrome (native feel). - Cleared on window close. Not persisted across reloads (matches native app behavior). ## 8. Migration phases Each phase is a separate PR, independently shippable. **Phase 1 — foundation** (no visible change) - Add `Route`, `Breadcrumb`, `HeaderAction`, `WindowRoute` context. - Add route fields to `WindowState`. - Add keyboard bindings for back/forward (no-op until routes exist). **Phase 2 — unified header** (visible change: Files-style chrome everywhere) - Build `window_header.rs`. - Replace both branches in `window.rs:393-551` with single header render. - Delete hardcoded headerless list (`main.rs:1973`). - Delete `.win-header`, `.win-controls` CSS. - Islands without route set: header shows metadata name + icon only. **Phase 3 — Files proof-of-concept** - Files drops internal `IslandHeader` call. - Files writes to `WindowRoute` on navigation. - Validate drag, back/forward, breadcrumb click. **Phase 4 — iframe protocol + shrimp proof-of-concept** - Ship `hero-window-route.js` helper. - Wire postMessage listener in window. - Update `/hero_agent/ui` to emit `hero:route`. **Phase 5 — URL serialization** - Add `RouteSerde` trait, implement for Files + shrimp. - Hash-fragment parser / writer in `hero_os_app`. - Replay on load. **Phase 6 — sweep remaining 15 native islands** - Mechanical: delete `IslandHeader` internal calls, add `use_window_route` calls. - ~30 min per island. **Phase 7 — sweep remaining ~24 iframe islands** - Add `hero-window-route.js` import + minimal breadcrumb push. - ~15 min per island. **Phase 8 — cleanup** - Confirm no remaining callers, remove dead CSS and helpers. ## 9. Non-goals (deferred) - Browser-style arrow buttons in header. - Route stack in URL. - Origin allowlist for postMessage (iframes going away long-term). - Arbitrary Dioxus elements / HTML in action slot. - Per-window tab bar (multi-route per window). ## 10. Open during implementation - Exact icon format for `HeaderAction.icon` — inline SVG string, lucide icon id, or island-defined asset path? Propose lucide id with SVG fallback. Lock in Phase 2. - Stack depth 50 — arbitrary, tune later. --- Ready for review. On approval, I open PR for Phase 1.
Author
Owner

Implementation complete — all 8 phases landed

hero_archipelagos 35-window-route-types — 6 commits
hero_os 35-unified-window-chrome — 5 commits

Per-phase summary

# Phase hero_os hero_archipelagos
1 Foundation — Route types, WindowRouteContext registry, Cmd+[/] bindings
2 Unified WindowHeader + delete headerless branch
2.5 Move island-facing types to hero_archipelagos_core
3 Files POC — use_window_route + path breadcrumbs + deep-link
4 iframe postMessage bridge + hero-window-route.js helper + shrimp wiring
5 URL deep-linking via existing routing::replace_url + initial_url_segment
6 All 15 native islands auto-migrated — one-liner: IslandHeader now forwards to WindowRoute
7 All 14 iframe islands wrap src with with_window_id(...)
8 Cleanup — nothing dead left from prior phases

Merge order

  1. hero_archipelagos#35-window-route-types → development
  2. Remove the temporary [patch] block at the bottom of hero_os/Cargo.toml; bump hero_archipelagos_core dep revision.
  3. hero_os#35-unified-window-chrome → development

Outside-scope work in downstream UIs

The iframe protocol is in place host-side. Embedded UIs in other repos opt in by serving hero-window-route.js and calling HeroRoute.push({ breadcrumbs, actions }) on internal nav. Each service's adoption is a few lines — tracked separately:

  • hero_agent (shrimp), hero_proc, hero_office, hero_foundry, hero_voice, hero_compute, hero_db (redis), hero_indexer, hero_auth_ui, hero_embedder, hero_aibroker, hero_proxy admin, hero_router/inspector

Until each is updated, those iframes stay headerless in the unified chrome — the OS still shows the island's metadata icon + name as a fallback.

Open UX questions for follow-up PRs

  • Icon format for HeaderAction.icon — currently accepts inline SVG; lucide-id support is a small follow-up if preferred.
  • IslandHeader component is now misleadingly named (no longer renders a header); eventual rename / deprecation worth scheduling.
  • Route stack depth (50) is arbitrary — tune if needed.
  • Mobile header currently forces hover=true so controls stay visible — revisit if touch UX shifts.
# Implementation complete — all 8 phases landed **hero_archipelagos** [`35-window-route-types`](https://forge.ourworld.tf/lhumina_code/hero_archipelagos/compare/development...35-window-route-types) — 6 commits **hero_os** [`35-unified-window-chrome`](https://forge.ourworld.tf/lhumina_code/hero_os/compare/development...35-unified-window-chrome) — 5 commits ## Per-phase summary | # | Phase | hero_os | hero_archipelagos | |---|---|---|---| | 1 | Foundation — Route types, WindowRouteContext registry, Cmd+[/] bindings | ✅ | — | | 2 | Unified WindowHeader + delete headerless branch | ✅ | — | | 2.5 | Move island-facing types to `hero_archipelagos_core` | ✅ | ✅ | | 3 | Files POC — `use_window_route` + path breadcrumbs + deep-link | — | ✅ | | 4 | iframe `postMessage` bridge + `hero-window-route.js` helper + shrimp wiring | ✅ | ✅ | | 5 | URL deep-linking via existing `routing::replace_url` + `initial_url_segment` | ✅ | ✅ | | 6 | **All 15 native islands auto-migrated** — one-liner: `IslandHeader` now forwards to WindowRoute | — | ✅ | | 7 | All 14 iframe islands wrap src with `with_window_id(...)` | — | ✅ | | 8 | Cleanup — nothing dead left from prior phases | — | — | ## Merge order 1. **`hero_archipelagos#35-window-route-types` → development** 2. Remove the temporary `[patch]` block at the bottom of `hero_os/Cargo.toml`; bump `hero_archipelagos_core` dep revision. 3. **`hero_os#35-unified-window-chrome` → development** ## Outside-scope work in downstream UIs The iframe protocol is in place host-side. Embedded UIs in other repos opt in by serving [`hero-window-route.js`](https://forge.ourworld.tf/lhumina_code/hero_archipelagos/src/branch/35-window-route-types/core/assets/hero-window-route.js) and calling `HeroRoute.push({ breadcrumbs, actions })` on internal nav. Each service's adoption is a few lines — tracked separately: - hero_agent (shrimp), hero_proc, hero_office, hero_foundry, hero_voice, hero_compute, hero_db (redis), hero_indexer, hero_auth_ui, hero_embedder, hero_aibroker, hero_proxy admin, hero_router/inspector Until each is updated, those iframes stay headerless in the unified chrome — the OS still shows the island's metadata icon + name as a fallback. ## Open UX questions for follow-up PRs - Icon format for `HeaderAction.icon` — currently accepts inline SVG; lucide-id support is a small follow-up if preferred. - `IslandHeader` component is now misleadingly named (no longer renders a header); eventual rename / deprecation worth scheduling. - Route stack depth (50) is arbitrary — tune if needed. - Mobile header currently forces hover=true so controls stay visible — revisit if touch UX shifts.
timur closed this issue 2026-04-15 10:20:40 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_os#35
No description provided.