Skip to content

Ark plan

Ark: in Halo lore, the Forerunner installation that forges the Halo rings and holds the means to rebuild everything. A monorepo is both — the place where shared code is built and the place that contains every project. Pairs with mjolnir (the Spartan armor / our workspace + agent tool).

Repo: 7hoenix/ark (personal account for now; transfer to an org later is lossless).


A single Tauri + Elm + Rust monorepo that consolidates 7hoenix projects onto one best-of-breed architecture, with shared packages as the convergence target. New apps are just new directories on the golden path; shared improvements land once and propagate.

2. Core philosophy — golden path + incremental convergence

Section titled “2. Core philosophy — golden path + incremental convergence”

The projects are iterations of the same ideas (ChessTrained → Trained; Planter → Functor). Each rev got better. Rather than freeze whichever app invented a pattern, we:

  1. Define the target architecture (“the golden path”) by harvesting the most-evolved version of each cross-cutting concern across projects.
  2. Extract shared packages that embody those best versions (synthesis, not copy-paste).
  3. Converge each app incrementally — one app, one concern at a time, each move a PR + changeset — until every app rides the shared packages.

The end state is shared packages as the single source of truth, with each app holding only its own domain on top.

ConcernChoiceWhy
Task runnermoonLanguage-agnostic; affected-only task graph + caching; one moon run interface. Native toolchains: Bun/Node, Rust (cargo). (Go is supported by moon but unused — mjolnir is all Rust.) Elm is NOT native — it runs as plain moon tasks wrapping the Elm CLI (elm make/elm-watch/elm-test/elm-review), with inputs/outputs declared by hand for caching. Elm tooling comes from bun devDeps (as jg already does).
Workspace backendbunAlready pinned in mise; fast; native workspaces for the JS/TS parts (worker, docsite, tooling).
Toolchain pinningmiseAlready in use; source of truth for language versions.
Versioning / changelogKnopeChangeset-style markdown-per-change, but language-agnostic — the highest-value shared packages are Elm/Rust, which npm-keyed Changesets can’t version. Same DX, feeds the docsite changelog.
DocsiteAstro StarlightStatic, fast, zero-JS-by-default docs site with built-in search; renders package changelogs.
Docsite host + authCloudflare Pages + Access (Zero Trust)Policy: email ends with @7hoenix.com. No app-side auth code.
VCS / workflowjj + mjolnirUnchanged. One mjolnir project instead of four; workspaces are isolated checkouts of the one repo.
ark/
├── .changeset|.knope/ # changeset-style records (Knope config)
├── .github/workflows/ # CI (moon affected) + release (Knope)
├── .claude/
│ ├── agents/ # per-project-type agents
│ └── CLAUDE.md # repo-wide conventions
├── .moon/ # workspace.yml, toolchain.yml
├── apps/
│ ├── jg/ # SRS app (golden-path reference)
│ ├── trained/ # chess training
│ ├── planter/ # roguelike-for-learning-programming
│ └── planter-marketing-*/ # the 3 marketing sites (TBD: keep or fold)
├── packages/
│ ├── app-core/ # Elm: Effect/Command/Reply/Js/Page/Auth
│ ├── ui/ # Elm: design system + Ui.Tokens + Tailwind theme
│ ├── tauri-core/ # Rust: crates/{api,core} + generate_elm
│ ├── worker/ # TS: Cloudflare Worker auth/JWT
│ └── chess-core/ # Elm/Rust: chess domain
├── docs/ # Astro Starlight (gated docsite)
├── mise.toml
├── package.json # bun workspaces
└── moon.yml

Comparison of jg / Trained / Planter found jg is the most-evolved on 7 of 8 shared concerns and the most AI-navigable. The golden path = jg’s patterns, extracted.

ConcernStandard (source)Notes
Elm app architecturejgExplicit Command/Reply dispatch (no wildcards — compile error forces you to handle new variants), Effect.elm, Js.elm ports, Page/ routing
Design system / Bookjg14-chapter elm-book + Ui.Tokens swap point
Rust↔Elm codegenjg (+ Trained’s import cleanup)elm_rs + explicit generate_elm binary (every type hand-listed), Response deliberately freeform JSON, build.rs iOS linker/env baking
Auth + backendjgCloudflare Worker: Apple OAuth, HS256 JWT + 90-day sliding expiry, R2 event store
Build / iOSjgmakefile + dev-web harness (same Rust core + Elm as Tauri, routed over HTTP), env-var baking dev/prod
TestingjgPlaywright e2e (dev-web driven, workers=1, retries=0); add elm-review (neither project has strong linting yet)
StylingjgTailwind v4 @theme tokens, class-based dark mode, safe-area insets, semantic animations
Domainapp-localStays in each app — see §7

Elm package mechanism: Elm has no private/local packages. Shared Elm lives in packages/<name>/src and each app lists it in elm.json source-directories. Versioned at the repo level by Knope, not published to the Elm registry.

PackageLangBest-of-breed sourceContents
app-coreElmjgEffect / Command / Reply dispatch, Js port interop, Page routing, Auth state machine
uiElmjg (supersedes Planter’s 7hoenix/u7)elm-book design system, Ui.Tokens, Tailwind v4 @theme. Mine u7 (Button/Dropdown/Text/Tooltip/Animation/Effects) for ideas.
tauri-coreRustjg (+ Trained import cleanup)crates/{api,core} split, generate_elm codegen, build.rs iOS/env
workerTSjgCloudflare Worker: OAuth, JWT + sliding expiry, R2 event store
chess-coreElm/RustTrainedChess/ (Board, Move, Square, Arrow), FEN/PGN, Stockfish glue
  • jg — Spaced-Repetition System: Entries, Firings, Interactions, Rotation, Reclassification. (Tightly coupled — SRS is the app.)
  • trained — Chess training: Chess/ + Coach/ + Train/, Stockfish (lambda), scenario pipeline, iOS.
  • planter — Roguelike for learning programming: game logic, levels, audio, physics, asset/Blender pipeline.

Two separate orderings (don’t conflate them):

  • Convergence order (apps adopting shared packages): jg → Trained → Planter. mjolnir is not on this track.
  • Move-in order (source landing in the repo): jg → mjolnir → Trained → Planter. mjolnir moves second because it’s the cheapest migration and unlocks dogfooding.
AppStack todayLiftPlan
jgelm-watch, elm_rs, Tailwind v4, Tauri v2referenceExtract its patterns into the shared packages
trainedsame toolchain as jglightAdopt shared ui/app-core; contribute chess-core; align codegen
planterVite + vite-plugin-elm, elm-ts-interop, Tailwind v2 + SCSS, Tauri v2heavy (toolchain modernization, not engine)Migrate Vite→elm-watch, elm-ts-interop→elm_rs, Tailwind v2→v4. Its 7hoenix/u7 package feeds ui. Ships 3 marketing sites (keep as apps or fold — TBD).
  • mjolnir — joins as apps/mjolnir. Already cleanly layered (engine libs mj-core/mj-events/mj-agents/mj-session/mj-vcs/mj-views, an HTTP mj-daemon, frontends mj-cli/mj-tui/mj-app). It’s a golden-path consumer, not an island: its Tauri+Elm frontend (mj-app, already excluded from the cargo workspace like jg’s src-tauri) rides app-core/ui/tauri-core (+ worker for remote auth); the engine crates are the app-local backend. Event-sourced like jg → shares the architectural style app-core encodes. Move in second (right after jg): engine + desktop app are a cheap colocation move; the mobile (iOS) + remote frontend — mj-app talking to mj-daemon over HTTP for remote workspace/agent control — is built once the shared packages mature. Full architecture + self-hosting dev loop: §17.
  • The bare Godot stub in Planter’s Engine/ is unrelated — ignore/delete on migration.

9. Versioning & docsite changelog flow (Knope)

Section titled “9. Versioning & docsite changelog flow (Knope)”
  1. Change a package/app → write a changeset record (markdown: which component, bump, summary).
  2. Merge PR with the record included.
  3. Release workflow runs Knope → bumps versions, rolls summaries into per-component CHANGELOG.md, opens/updates a release PR.
  4. Docsite reads those changelogs → renders “what changed” per app/package. This is the work-style flow, language-agnostic.
  • docs/ = Astro Starlight; aggregates each app’s elm-book + changelogs into one living portfolio.
  • Deploy to Cloudflare Pages.
  • Zero Trust → Access application over the Pages domain; policy = Allow if email ends with @7hoenix.com. Login wall, no code.
  • ~/.mjolnir/projects.toml collapses to one ark project (merge_strategy = "push_to_main").
  • Existing flow unchanged: jj + workspaces, push / post-merge skills.
  • New app = new apps/ dir, not a new mjolnir project.

.claude/agents/ checked into the repo, traveling with every workspace/PR:

  • elm-tauri-app — knows the Effect/Command/Js golden path
  • worker — Cloudflare/jose/JWT
  • chess — chess-core + Stockfish
  • docs — Starlight + changelogs

Per-app CLAUDE.md layers specifics. (jg/Trained already have strong CLAUDE.mds to seed these.)

  • One .github/workflows: PR CI = moon ci (affected-only graph + cache); merge-to-main = Knope release.
  • Every app/package flows the identical PR → changeset → release path.
  • Phase 0 — Repo: gh repo create 7hoenix/ark, jj git init, .gitignore, mise.toml, LICENSE, README. (Outward-facing — confirm before running.)
  • Phase 1 — Skeleton: bun workspaces, .moon/*, one example app + package to prove linking + task graph + cache end to end.
  • Phase 2 — Golden-path extraction + mjolnir: move jg in; extract app-core, ui, tauri-core, worker from it. Then move mjolnir in (cheapest migration; proves the standalone-Rust path + starts dogfooding — see §17).
  • Phase 3 — Versioning + docsite: Knope init; Starlight scaffold reading changelogs; release workflow.
  • Phase 4 — Cloudflare gate: Pages deploy + Access @7hoenix.com policy.
  • Phase 5 — Agents: .claude/agents/ roster.
  • Phase 6 — Converge the rest: Trained (light; contribute chess-core) → Planter (toolchain modernization; fold u7 into ui; decide marketing sites). Collapse projects.toml as each lands. (mjolnir already moved in Phase 2.)
  • Functor (Godot) — stays its own repo + mjolnir project. Merge in later (with Git LFS, as an opaque moon project) only if it starts sharing the Rust core / mcp-server.
  • game-core package — deferred until a second game justifies it.
  • Engine decision (Tauri vs Godot) for the game family — deferred; revisit when reviving Functor.
  • ChessTrained — dropped; its useful parts already folded into Trained.
  • Does mjolnir live inside Ark? Colocation island or golden-path? Yes — apps/mjolnir, moves in 2nd, and it’s a golden-path consumer (mobile/remote frontend on app-core/ui/tauri-core/worker; see §8 + §17).
  • Planter marketing sites (×3): separate apps/ or folded into planter?
  • elm-review config: standardize one ruleset across apps (neither jg nor Trained has strong linting today).
  • Package namespace for published Elm (7hoenix/ui, etc.) vs source-dirs-only.

17. Developing mjolnir inside Ark (architecture + self-hosting pattern)

Section titled “17. Developing mjolnir inside Ark (architecture + self-hosting pattern)”

mjolnir intends to grow a mobile app on the golden path for remote workspace/agent control. It’s already structured for this.

Current layering (maps straight onto Ark):

  • Engine libs — mj-core, mj-events (event-sourced + replay bin), mj-agents, mj-session, mj-vcs (jj), mj-views. App-local backend crates (promote to packages/ only if reused elsewhere).
  • mj-daemon (lib+bin) — HTTP API (/api/snapshot, /api/events), address via MJ_BIND. The client/server seam already exists.
  • Frontends — mj-cli, mj-tui, and mj-app (Tauri; already excluded from the cargo workspace, like jg’s src-tauri).

Target shape:

  • apps/mjolnir/ = engine crates + daemon + cli + tui (existing Rust).
  • mj-app (Tauri+Elm frontend) rides the golden path: app-core (event-sourced architecture — shared with jg), ui, tauri-core (Rust→Elm codegen for the daemon’s API types).
  • Mobile = the same frontend extended to iOS (jg/Trained already have the Tauri iOS path), talking to mj-daemon over HTTP for remote control.
  • Remote auth: secure the daemon API with the worker/JWT package (reused from jg) + a tunnel. Never expose an unauthenticated daemon.

So mjolnir is the richest golden-path consumer: app-core + ui + tauri-core + worker, over a large app-local engine.

Self-hosting dev loop (ouroboros made ergonomic):

  • Stable binary (cargo install / released) manages real workspaces against ~/.mjolnir.
  • Dev build (apps/mjolnir target) runs against isolated state + alt port so it never touches real state:
    • MJ_BIND=127.0.0.1:<altport> — already supported ✅
    • MJOLNIR_HOME=~/.mjolnir-devneeds adding: route all state resolution (db, projects.toml, workspaces) through one env-overridable home-dir fn (today ~/.mjolnir is effectively hardcoded).
    • A fixture/sandbox project as the dev instance’s only managed project.
  • Promotion = release: cargo install --path apps/mjolnir (or tagged release) → new stable binary. mjolnir’s artifact in the Knope flow.
  • Rule: never point a dev build at ~/.mjolnir while it’s managing real work.

Concrete first enabling task: add MJOLNIR_HOME (state-dir override) — the missing piece that makes the dev loop safe.