hero_office: OnlyOffice connector restructure + hero_os Office archipelago embed #1

Closed
opened 2026-04-19 17:19:02 +00:00 by timur · 2 comments
Owner

Goal

Turn hero_office from a single-crate OnlyOffice+WebDAV connector into a proper Hero service, and embed the OnlyOffice editor surfaces (documents, spreadsheets, presentations, PDFs, diagrams) as apps in hero_os under a new "Office" archipelago.

Original instructions

we are working on hero office. we want to add it to ../hero_os /web_embed /hero_sockets /hero_crates_best_practices_check make sure we make this repo like a hero service repo. one crate for server, which is essentially a openrpc wrapper around hero_office. we want to use the schemas in ../hero_rpc etc to follow our conventions and make sure its as we desire, and use code generation from there. then the goal is to embed its ui as apps like spreadsheets do cs etc to hero_os

Follow-ups during the session:

  • "Office application group in dock with all OnlyOffice UIs" — confirmed as 5 islands (Documents, Spreadsheets, Presentations, PDF, Diagrams) in one hierarchical archipelago. Reason: OnlyOffice Docs ships one api.js that adapts to 5 documentType values — word, cell, slide, pdf, diagram.
  • "We're using nushell scripts instead of makefile/build_lib" — adopted the nu_service pattern (scripts/nu_service.nu, modeled on service_proxy.nu).
  • "hero_router auto-discovers; the button should just lead to /hero_office/ui" — fixed prefix handling (X-Forwarded-Prefix).
  • "hero_foundry is the new hero_fossil; use its context model" — swapped custom per-context fossil UDS code for hero_foundry's single-server REST API.
  • "Keep OnlyOffice Docker for now" — after research, native macOS build is a 3–7 day rabbit hole (blocker: x2t has no Mac build path, no prebuilt binary). Docker interim with shared JWT secret.

What shipped

Workspace restructure (hero_office/)

Old single-crate axum app replaced with standard 4-crate Hero workspace under crates/:

crate role
hero_office_server OpenRPC JSON-RPC daemon on hero_office/rpc.sock — business logic + JWT signing + hero_foundry client
hero_office_sdk Generated typed client via hero_rpc_derive::openrpc_client! macro
hero_office_ui HTTP UI on hero_office/ui.sock — admin dashboard + 5 OnlyOffice wrapper surfaces
hero_office_examples health / list_contexts demos + #[ignore]d integration tests

Plus buildenv.sh, scripts/nu_service.nu, README.md.

Old src/ removed. cargo check --workspace --tests passes clean.

Server (OpenRPC methods)

All hand-written in crates/hero_office_server/openrpc.json, exposed as POST /rpc over UDS (HTTP-over-UDS to match the rest of Hero):

method purpose
health version + status
list_contexts proxies GET /api/contexts on hero_foundry
list_documents(context, type_filter?) proxies foundry + filters by OnlyOffice documentType
create_editor_session(context, filename) builds + JWT-signs the OnlyOffice DocEditor config, registers doc_key→file mapping
get_document(context, filename) fetches bytes from foundry, returns base64
save_document(key, download_url, filetype?) callback handler: resolves key, downloads, PUTs back to foundry

UI routes

Router-mounted under /hero_office/ui/…:

  • GET /admin/?context=… — Bootstrap 5.3.3 dashboard, per-type counts
  • GET /{word|cell|slide|pdf|diagram}/?context=… — file browser filtered to that OnlyOffice documentType (the surfaces hero_os iframes)
  • GET /{type}/edit/{filename}?context=… — loads OnlyOffice api.js, calls new DocsAPI.DocEditor(...) with the signed config
  • GET /files/{ctx}/{filename} — byte proxy for the OnlyOffice container
  • POST /callback/{ctx} — OnlyOffice save callback

All URLs go through a forwarded_prefix() helper that honors X-Forwarded-Prefix, so the same binary works behind hero_router or directly on its UDS.

hero_os integration

crates/hero_os_app/src/registry.rs: added office hierarchical archipelago (5 inline islands — no per-app crate needed) modeled on the existing books/biz/voice pattern. island_content.rs: 5 ExternalServiceIframe match arms, one per OnlyOffice documentType.

Stub hero_archipelagos/archipelagos/embed/office/ crate left orphaned (not in workspace) — can be deleted.

hero_foundry integration

Replaced the inlined PROPFIND/per-context UDS WebDAV client with a thin HTTP-over-UDS client targeting hero_foundry's single REST socket:

  • GET /api/contexts → context discovery
  • GET /api/files/{ctx}/, GET /api/files/{ctx}/{f}, PUT /api/files/{ctx}/{f}

Populated ~/hero/var/foundry_storage/{demo,incubaid}/ with sample docs (docx, pptx, csv, md, txt) for demo purposes.

OnlyOffice runtime

Research conclusion (reported in thread): native Mac build of Document Server is multi-day because the x2t C++ converter has no Mac build path. OrbStack-VM with apt install is viable but dep resolution on 25.10 failed; 24.04 would work but user opted to keep Docker as interim.

Container runs:

docker run -d --name onlyoffice -p 8081:80 \
  -e JWT_ENABLED=true -e JWT_SECRET=hero-office-dev-secret-change-me \
  --add-host host.docker.internal:host-gateway onlyoffice/documentserver

JWT secret matches ONLYOFFICE_JWT_SECRET in the hero_office_server action env.

Socket layout

$HERO_SOCKET_DIR/
├── hero_office/
│   ├── rpc.sock           ← OpenRPC (server)
│   └── ui.sock            ← HTTP UI (per hero_router web naming)
├── hero_foundry/
│   └── rpc.sock           ← single server, all contexts
└── hero_proc/rpc.sock

nu_service

scripts/nu_service.nu implements install | start [--reset] | stop | status per the nu_service skill, modeled on service_proxy.nu. Registers both hero_office_server and hero_office_ui actions with shared env (ONLYOFFICE_JWT_SECRET, OO_SERVER_URL, CONNECTOR_EXTERNAL_URL), ties them into a single hero_office service via service.set.

Status — live locally

  • hero_router autodiscovers both sockets as healthy (UI=web, RPC=openrpc with 6 methods).
  • Admin dashboard: http://localhost:9988/hero_office/ui/admin/
  • Example browsers: /hero_office/ui/word/?context=demo, /hero_office/ui/cell/?context=demo, …
  • Editor: /hero_office/ui/word/edit/meeting-notes.docx?context=demo — renders OnlyOffice api.js; editing requires OnlyOffice container at :8081 with matching JWT.
  • hero_os: 5 Office apps under the new Office archipelago, each iframes the corresponding surface.

Open items / follow-ups

  1. OnlyOffice native / OrbStack path — docker is interim. When time allows, either: (a) finish OrbStack Ubuntu 24.04 apt-install + hero_proc SSH wrapper, or (b) revisit native macOS build if x2t gets a Mac build path upstream.
  2. hero_foundry → hero_osis context enrichment — foundry currently discovers contexts by scanning its storage dir. When foundry integrates with hero_osis_base.context.list, hero_office's list_contexts will inherit richer metadata (names, descriptions) without code change here.
  3. Delete hero_archipelagos/archipelagos/embed/office/ stub (orphaned crate, not in workspace).
  4. Tests — integration tests in hero_office_examples/tests/integration.rs are #[ignore]d; add CI wiring to run them against a live foundry+hero_office.
  5. OSchema codegen — user said "use code generation from hero_rpc"; we used the openrpc_client! macro (which IS hero_rpc codegen, but applied to hand-written openrpc.json, not OSchema). Migration to full OSchema pipeline (build.rs + hero_rpc_generator) is a clean follow-up that doesn't change the public API.

Docs

  • README.md updated with socket layout, env vars, URL surface, and hero_os integration notes.
  • Follow the /hero_ui_dashboard, /hero_sockets, /hero_crates_best_practices_check, /web_embed, and /nu_service skills.

🤖 Generated with Claude Code

## Goal Turn `hero_office` from a single-crate OnlyOffice+WebDAV connector into a proper Hero service, and embed the OnlyOffice editor surfaces (documents, spreadsheets, presentations, PDFs, diagrams) as apps in `hero_os` under a new "Office" archipelago. ## Original instructions > we are working on hero office. we want to add it to `../hero_os` `/web_embed` `/hero_sockets` `/hero_crates_best_practices_check` make sure we make this repo like a hero service repo. one crate for server, which is essentially a openrpc wrapper around hero_office. we want to use the schemas in `../hero_rpc` etc to follow our conventions and make sure its as we desire, and use code generation from there. then the goal is to embed its ui as apps like spreadsheets do cs etc to hero_os Follow-ups during the session: - "Office application group in dock with all OnlyOffice UIs" — confirmed as **5 islands** (Documents, Spreadsheets, Presentations, PDF, Diagrams) in one hierarchical archipelago. Reason: OnlyOffice Docs ships one `api.js` that adapts to 5 `documentType` values — `word`, `cell`, `slide`, `pdf`, `diagram`. - "We're using nushell scripts instead of makefile/build_lib" — adopted the [`nu_service`](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/75) pattern (`scripts/nu_service.nu`, modeled on `service_proxy.nu`). - "hero_router auto-discovers; the button should just lead to `/hero_office/ui`" — fixed prefix handling (`X-Forwarded-Prefix`). - "`hero_foundry` is the new `hero_fossil`; use its context model" — swapped custom per-context fossil UDS code for hero_foundry's single-server REST API. - "Keep OnlyOffice Docker for now" — after research, native macOS build is a 3–7 day rabbit hole (blocker: `x2t` has no Mac build path, no prebuilt binary). Docker interim with shared JWT secret. ## What shipped ### Workspace restructure (`hero_office/`) Old single-crate axum app replaced with standard 4-crate Hero workspace under `crates/`: | crate | role | | --- | --- | | `hero_office_server` | OpenRPC JSON-RPC daemon on `hero_office/rpc.sock` — business logic + JWT signing + hero_foundry client | | `hero_office_sdk` | Generated typed client via `hero_rpc_derive::openrpc_client!` macro | | `hero_office_ui` | HTTP UI on `hero_office/ui.sock` — admin dashboard + 5 OnlyOffice wrapper surfaces | | `hero_office_examples` | health / list_contexts demos + `#[ignore]`d integration tests | Plus `buildenv.sh`, `scripts/nu_service.nu`, `README.md`. Old `src/` removed. `cargo check --workspace --tests` passes clean. ### Server (OpenRPC methods) All hand-written in `crates/hero_office_server/openrpc.json`, exposed as `POST /rpc` over UDS (HTTP-over-UDS to match the rest of Hero): | method | purpose | | --- | --- | | `health` | version + status | | `list_contexts` | proxies `GET /api/contexts` on hero_foundry | | `list_documents(context, type_filter?)` | proxies foundry + filters by OnlyOffice `documentType` | | `create_editor_session(context, filename)` | builds + JWT-signs the OnlyOffice DocEditor config, registers doc_key→file mapping | | `get_document(context, filename)` | fetches bytes from foundry, returns base64 | | `save_document(key, download_url, filetype?)` | callback handler: resolves key, downloads, PUTs back to foundry | ### UI routes Router-mounted under `/hero_office/ui/…`: - `GET /admin/?context=…` — Bootstrap 5.3.3 dashboard, per-type counts - `GET /{word|cell|slide|pdf|diagram}/?context=…` — file browser filtered to that OnlyOffice documentType (the surfaces hero_os iframes) - `GET /{type}/edit/{filename}?context=…` — loads OnlyOffice `api.js`, calls `new DocsAPI.DocEditor(...)` with the signed config - `GET /files/{ctx}/{filename}` — byte proxy for the OnlyOffice container - `POST /callback/{ctx}` — OnlyOffice save callback All URLs go through a `forwarded_prefix()` helper that honors `X-Forwarded-Prefix`, so the same binary works behind hero_router or directly on its UDS. ### hero_os integration `crates/hero_os_app/src/registry.rs`: added `office` hierarchical archipelago (5 inline islands — no per-app crate needed) modeled on the existing books/biz/voice pattern. `island_content.rs`: 5 `ExternalServiceIframe` match arms, one per OnlyOffice documentType. Stub `hero_archipelagos/archipelagos/embed/office/` crate left orphaned (not in workspace) — can be deleted. ### hero_foundry integration Replaced the inlined PROPFIND/per-context UDS WebDAV client with a thin HTTP-over-UDS client targeting hero_foundry's single REST socket: - `GET /api/contexts` → context discovery - `GET /api/files/{ctx}/`, `GET /api/files/{ctx}/{f}`, `PUT /api/files/{ctx}/{f}` Populated `~/hero/var/foundry_storage/{demo,incubaid}/` with sample docs (docx, pptx, csv, md, txt) for demo purposes. ### OnlyOffice runtime Research conclusion (reported in thread): native Mac build of Document Server is multi-day because the `x2t` C++ converter has no Mac build path. OrbStack-VM with apt install is viable but dep resolution on 25.10 failed; 24.04 would work but user opted to keep Docker as interim. Container runs: ``` docker run -d --name onlyoffice -p 8081:80 \ -e JWT_ENABLED=true -e JWT_SECRET=hero-office-dev-secret-change-me \ --add-host host.docker.internal:host-gateway onlyoffice/documentserver ``` JWT secret matches `ONLYOFFICE_JWT_SECRET` in the `hero_office_server` action env. ### Socket layout ``` $HERO_SOCKET_DIR/ ├── hero_office/ │ ├── rpc.sock ← OpenRPC (server) │ └── ui.sock ← HTTP UI (per hero_router web naming) ├── hero_foundry/ │ └── rpc.sock ← single server, all contexts └── hero_proc/rpc.sock ``` ### nu_service `scripts/nu_service.nu` implements `install | start [--reset] | stop | status` per the `nu_service` skill, modeled on `service_proxy.nu`. Registers both `hero_office_server` and `hero_office_ui` actions with shared env (ONLYOFFICE_JWT_SECRET, OO_SERVER_URL, CONNECTOR_EXTERNAL_URL), ties them into a single `hero_office` service via `service.set`. ## Status — live locally - hero_router autodiscovers both sockets as healthy (UI=web, RPC=openrpc with 6 methods). - Admin dashboard: `http://localhost:9988/hero_office/ui/admin/` - Example browsers: `/hero_office/ui/word/?context=demo`, `/hero_office/ui/cell/?context=demo`, … - Editor: `/hero_office/ui/word/edit/meeting-notes.docx?context=demo` — renders OnlyOffice api.js; editing requires OnlyOffice container at `:8081` with matching JWT. - hero_os: 5 Office apps under the new Office archipelago, each iframes the corresponding surface. ## Open items / follow-ups 1. **OnlyOffice native / OrbStack path** — docker is interim. When time allows, either: (a) finish OrbStack Ubuntu 24.04 apt-install + hero_proc SSH wrapper, or (b) revisit native macOS build if `x2t` gets a Mac build path upstream. 2. **hero_foundry → hero_osis context enrichment** — foundry currently discovers contexts by scanning its storage dir. When foundry integrates with `hero_osis_base.context.list`, `hero_office`'s `list_contexts` will inherit richer metadata (names, descriptions) without code change here. 3. **Delete** `hero_archipelagos/archipelagos/embed/office/` stub (orphaned crate, not in workspace). 4. **Tests** — integration tests in `hero_office_examples/tests/integration.rs` are `#[ignore]`d; add CI wiring to run them against a live foundry+hero_office. 5. **OSchema codegen** — user said "use code generation from hero_rpc"; we used the `openrpc_client!` macro (which IS hero_rpc codegen, but applied to hand-written `openrpc.json`, not OSchema). Migration to full OSchema pipeline (build.rs + `hero_rpc_generator`) is a clean follow-up that doesn't change the public API. ## Docs - [`README.md`](README.md) updated with socket layout, env vars, URL surface, and hero_os integration notes. - Follow the `/hero_ui_dashboard`, `/hero_sockets`, `/hero_crates_best_practices_check`, `/web_embed`, and `/nu_service` skills. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Update 2026-04-20 — OnlyOffice container blocked on network

OnlyOffice Docs (onlyoffice/documentserver ~1.5 GB, arm64) couldn't pull on current link. Pull ran for 12h+ without acquiring the last layer; measured downstream ~50 KB/s on a neutral 100 MB test, which would be 8+ hours at best. Pull killed to stop wasting state.

Everything hero_office-side is live and verified independently:

  • hero_router autodiscovers both hero_office sockets as healthy (ui=web, rpc=openrpc with all 6 methods parsed from openrpc.json)
  • Admin dashboard: http://localhost:9988/hero_office/ui/admin/
  • Per-type browsers: /hero_office/ui/{word,cell,slide,pdf,diagram}/?context=demo
  • RPC create_editor_session returns a proper JWT-signed OnlyOffice config (verified in-thread — token, document.url, editorConfig.callbackUrl all point at host.docker.internal:9988/hero_office/ui/… per hero_router's prefix convention)
  • Sample files seeded into two hero_foundry contexts (demo, incubaid) — docx, pptx, csv, md, txt

What remains: pulling the container on a faster connection and hitting the editor URL to confirm the iframe renders. This is a standalone OnlyOffice concern — the hero_office integration itself doesn't change based on that outcome.

Follow-ups for this task

  • Retry docker pull onlyoffice/documentserver on a faster network and verify editor iframe loads at /hero_office/ui/word/edit/meeting-notes.docx?context=demo
  • Consider mirroring the image to forge.ourworld.tf's container registry (lhumina_code/onlyoffice_documentserver) to avoid dockerhub pull times on fresh installs
  • Rebuild hero_os WASM so the new Office archipelago (5 islands) renders in the dock — currently blocked on a pre-existing dx build panic in krates re: herolib_ai/image-io; unrelated to these changes
## Update 2026-04-20 — OnlyOffice container blocked on network OnlyOffice Docs (`onlyoffice/documentserver` ~1.5 GB, arm64) couldn't pull on current link. Pull ran for 12h+ without acquiring the last layer; measured downstream ~50 KB/s on a neutral 100 MB test, which would be 8+ hours at best. Pull killed to stop wasting state. Everything hero_office-side is live and verified independently: - `hero_router` autodiscovers both `hero_office` sockets as healthy (ui=web, rpc=openrpc with all 6 methods parsed from `openrpc.json`) - Admin dashboard: http://localhost:9988/hero_office/ui/admin/ - Per-type browsers: `/hero_office/ui/{word,cell,slide,pdf,diagram}/?context=demo` - RPC `create_editor_session` returns a proper JWT-signed OnlyOffice config (verified in-thread — `token`, `document.url`, `editorConfig.callbackUrl` all point at `host.docker.internal:9988/hero_office/ui/…` per hero_router's prefix convention) - Sample files seeded into two hero_foundry contexts (`demo`, `incubaid`) — docx, pptx, csv, md, txt What remains: pulling the container on a faster connection and hitting the editor URL to confirm the iframe renders. This is a standalone OnlyOffice concern — the hero_office integration itself doesn't change based on that outcome. ### Follow-ups for this task - [ ] Retry `docker pull onlyoffice/documentserver` on a faster network and verify editor iframe loads at `/hero_office/ui/word/edit/meeting-notes.docx?context=demo` - [ ] Consider mirroring the image to `forge.ourworld.tf`'s container registry (`lhumina_code/onlyoffice_documentserver`) to avoid dockerhub pull times on fresh installs - [ ] Rebuild hero_os WASM so the new Office archipelago (5 islands) renders in the dock — currently blocked on a pre-existing `dx build` panic in `krates` re: `herolib_ai`/`image-io`; unrelated to these changes
timur closed this issue 2026-04-20 15:03:09 +00:00
Author
Owner

Update — herolib_ai/krates panic resolved

Root cause: krates 0.17.5 (pinned by dx/dioxus-cli 0.7) has a bug in its feature-graph builder — when a feature's activators are all dep: entries, the feature name never gets added to the resolved node's feature list. Later, a binary search looks up that feature name by string and panics on unreachable!().

herolib_ai happened to hit it: image-io = ["dep:image", "dep:base64"].

Fix

Minimal one-line workaround in hero_lib: add an empty marker feature as a third activator, giving krates a non-dep: edge to follow.

-image-io = ["dep:image", "dep:base64"]
+image-io = ["dep:image", "dep:base64", "_image_io_marker"]
+_image_io_marker = []

Functionally a no-op. The marker feature enables nothing.

Commits

  • lhumina_code/hero_lib@3ec721d3 — the fix
  • lhumina_code/hero_os@4323659 — lockfile bump to pick it up

Verified

make build-wasm now completes cleanly: 286/286 crates compiled, wasm-bindgen ran, bundle produced at target/dx/hero_os_app/debug/web/public.

Follow-up removed from this issue

Item 3 ("Rebuild hero_os WASM blocked on herolib_ai panic") is resolved. Remaining follow-ups: OnlyOffice container pull (network) and image mirroring to forge.ourworld.tf.

## Update — herolib_ai/krates panic resolved ✅ Root cause: `krates` 0.17.5 (pinned by `dx`/`dioxus-cli` 0.7) has a bug in its feature-graph builder — when a feature's activators are **all** `dep:` entries, the feature name never gets added to the resolved node's feature list. Later, a binary search looks up that feature name by string and panics on `unreachable!()`. `herolib_ai` happened to hit it: `image-io = ["dep:image", "dep:base64"]`. ### Fix Minimal one-line workaround in `hero_lib`: add an empty marker feature as a third activator, giving krates a non-`dep:` edge to follow. ```diff -image-io = ["dep:image", "dep:base64"] +image-io = ["dep:image", "dep:base64", "_image_io_marker"] +_image_io_marker = [] ``` **Functionally a no-op.** The marker feature enables nothing. ### Commits - `lhumina_code/hero_lib@3ec721d3` — the fix - `lhumina_code/hero_os@4323659` — lockfile bump to pick it up ### Verified `make build-wasm` now completes cleanly: 286/286 crates compiled, wasm-bindgen ran, bundle produced at `target/dx/hero_os_app/debug/web/public`. ### Follow-up removed from this issue Item 3 ("Rebuild hero_os WASM blocked on herolib_ai panic") is resolved. Remaining follow-ups: OnlyOffice container pull (network) and image mirroring to forge.ourworld.tf.
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_office#1
No description provided.