zudo-text

検索したい単語を入力

いつでも検索バーを開ける

ROOT app — generating leaf text apps

zudotext.app is the ROOT app: the full writing app plus a root-only generate surface that lets you create LEAF text apps — your own separately-named writing apps, each opening its own encrypted cloud workspace. ROOT hosts the ONE shared core that every LEAF uses; install it, launch it, use it as a writing app, and generate another text app whenever you want one.

Vocabulary

These words are used consistently across the UI and these docs:

  • zudo-text — the umbrella project. Contains the ROOT app, the writing-app renderer, the documentation site, and the shared packages.

  • ROOTzudotext.app itself. A thin launcher stub hosting the ONE shared core (Contents/Frameworks/libzudotext_core.dylib). Runs the full writing-app subsystems (editor, workspace sync, boards, AI) and additionally exposes the generate surface. The "generate" surface is ROOT-only; the writing surface runs in both ROOT and every LEAF.

  • workspace — the encrypted cloud store an app instance opens. Each instance is bound to exactly one, and owning several workspaces on one account (one per text app) is the normal shape.

  • LEAF — any text app generated by ROOT (modmsg.app, prompts.app, worktext.app, ztoffice.app, …). Each LEAF is a KB-scale stub bundle that shares ROOT's core via an absolute-path sidecar — it is not a self-contained copy. LEAF runs all writing subsystems; the generate surface is not exposed.

  • text app — general term for any LEAF app. Used in the UI and these docs when the specific LEAF name does not matter.

Role (ROOT vs LEAF) is determined at runtime from the .app bundle stem, not from any compile-time flag. See App Generation — Role Model for how this works.

Installing the ROOT app

The ROOT app ships as a standalone macOS .app bundle. You do not need to clone the repo, install Node.js, or install Rust to install and use it.

1. Download

Download zudotext.app from the project's distribution page:

Download: <project-distribution-page-tbd>

2. Install

Move the downloaded zudotext.app into your /Applications/ folder. macOS will then treat it like any other installed app — Spotlight finds it, Launchpad shows it.

Keep ROOT at its installed path. Every LEAF you generate stores the absolute path to ROOT's shared core. Moving or deleting ROOT after generating LEAFs will cause those LEAFs to exit at launch with a "shared core not found" error — re-generate them from the new ROOT location to fix.

Note on Gatekeeper: The ROOT app distributed to end users requires Developer-ID signing and notarization before distribution. This signing configuration does not exist today — it is a separate distribution prerequisite not covered by the current development cycle. For now the app is intended for developer use and direct install from the project repo.

3. First launch

Double-click zudotext.app. On a fresh install it opens onboarding: sign in, then create a workspace or connect one your account already owns. No local workspace directory or plaintext copy of your content is created on disk (the app does keep session state such as your workspace credentials locally) — see Cloud-first onboarding for the full flow. Once the workspace is unlocked, ROOT is an ordinary writing app.

When running as ROOT, a contextual leaf-app icon appears in the toolbar's right cluster; for a workspace that does not yet have a generated text app, it offers to generate one (see Generating a leaf text app below).

Already have an older zudotext.app installed? Update it in place at the same path — this lets all existing LEAFs pick up the new core automatically. To replace in place: quit the old one, then mv /Applications/zudotext.app /tmp/zudotext-old.app before copying the new one in. If you move ROOT to a different path instead, re-generate any existing LEAFs.

Switching between workspaces

Your account can own more than one workspace, and ROOT shows a workspace switcher rail along the left edge of the window so you can hot-swap between them without restarting. Click a tile to switch in-session, or click the rail's + tile (or run Create New Workspace… from the command palette, Mod+Alt+Shift+W) to mint a brand-new one. The rail is ROOT-only and hidden on mobile/iOS/zen mode, same as the rest of the window chrome; a LEAF never shows it, since a LEAF is already bound to exactly one workspace. See Workspace Switcher for the full walkthrough.

Each workspace can become its own LEAF app: right-click any tile in the rail for a quick "Generate leaf app…" / "Open <appName>.app" menu scoped to that workspace, without switching to it first — the same generate pipeline described below, just entered from a different door.

Generating a leaf text app

A LEAF text app is generated for a workspace of your choosing — by default the workspace you are currently in, but you can also attach a different workspace your account owns, or defer the choice to the new LEAF's own first launch (epic #4204 D2/D12, S23/#4227 — a generated instance points at a cloud workspace, not a local workspace directory).

Opening the dialog

When you run ROOT in a workspace that does not yet have a generated text app, a leaf-app icon (an app-with-plus glyph) appears in the toolbar's right cluster, separated from the other icons by a thin divider; clicking it opens the Generate leaf app dialog. (Its tooltip reads "Generate leaf app for this workspace" and acts on the active workspace.)

Once a LEAF has been generated for the workspace, the same toolbar slot flips to an "Open leaf app" launcher icon instead — clicking it launches the installed bundle rather than re-opening the generate dialog. This used to be the only way to reach the dialog, so it became unreachable once you had generated one app for a workspace (#4524). The dialog is now also reachable at any time via the command palette (Generate Leaf App…, ROOT-only) or a configurable shortcut (Settings → Shortcuts → App → "Generate Leaf App", no default binding).

The dialog — a single step

The dialog is deliberately lightweight: a single step with no wizard pages, no preset picker, and no settings tabs. It shows three fields:

  • App name (editable) — becomes the .app bundle stem (e.g. my-notesmy-notes.app) and cannot be changed after generation. Only lowercase letters, digits, and hyphens are accepted. The field may arrive pre-filled or empty: the default is still derived from the old project-root path, which no longer resolves to a meaningful name under the workspace model, so expect to type it yourself.

  • Display name (editable) — what shows in the macOS menu bar and Dock; it does not affect the bundle stem. Same caveat about the pre-filled default.

  • Workspace (a picker, #4524) — defaults to the same workspace you're already using (unchanged behavior if you leave it alone). You can instead pick a different workspace your signed-in account owns, or choose "Create a new workspace…". Minting a brand-new workspace (genesis) is never run from inside this dialog — doing so from mid-session would hijack ROOT's own live cloud-sync connection — so "Create a new workspace…" mints the LEAF unbound-with-intent instead: the fresh app records that intent and, on its own first launch, always offers the genesis/attach-a-workspace picker (even if your account happens to own exactly one other workspace, which would otherwise auto-attach silently). The "attach a different workspace" option is a read-only list fetch and never touches your other workspaces' content; if it fails to load (e.g. offline), the picker still offers this app's own workspace and "Create a new workspace…" — nothing is blocked. Right-clicking a tile in the workspace switcher rail opens this same dialog pre-locked to that tile's workspace, skipping this picker entirely.

There is no per-LEAF settings pre-configuration step: the generated app's settings — color scheme, editor, shortcuts, sync, AI features, and per-pin providerProps — live in the shared workspace document and are configured inside the generated LEAF app itself (the Manage header pins dialog sets per-pin paths, for example).

Click Generate leaf app to start. The button stays disabled until both the app name and the display name are valid.

What happens on generate

Submitting runs the no-compile assembly pipeline. No Rust toolchain, no cargo build, no source tree is needed:

  1. Stamp the stub — clone ROOT's prebuilt stub skeleton (a KB-scale Mach-O + template Info.plist + default .icns) into a hidden staging directory.

  2. Locate ROOT's shared core and verify it exists. If the core is absent, the generation stops immediately with a structured error.

  3. Write a Contents/Resources/core-path sidecar into the staged bundle — the absolute path to ROOT's core. This is what lets the stub find the core at launch.

  4. Edit the staged Info.plist to set the new CFBundleName, CFBundleDisplayName, CFBundleIdentifier, CFBundleExecutable, and CFBundleIconFile.

  5. Ad-hoc re-sign the staged bundle (codesign --force --sign -).

  6. Strip the quarantine xattr from the staged bundle.

  7. Move the staged bundle to ~/Applications/{name}.app (the per-user Applications folder).

  8. Write ~/.config/zudotext/{name}/config.json (schema v2) binding the new LEAF to the picked workspace — or, if you chose "Create a new workspace…", write its new-workspace-intent marker instead (#4524).

A progress log streams each step inside the dialog; assembly completes in seconds (no compile wait). On success the dialog reports the installed path and the new LEAF app is ready to launch — double-click it from Finder or open it via Spotlight. If a LEAF with the same name already exists, the dialog asks whether to Replace it.

On failure, the error stays on screen. If the failure happened after step 7 (the bundle is already installed but step 8's write failed), the LEAF is still a valid, launchable app — it just boots to onboarding on first launch instead of opening a specific workspace directly, the same as any freshly generated, not-yet-bound LEAF. Most failures are path/permission issues — check that ~/Applications/ is writable and that you are not overwriting a currently-running LEAF app bundle.

Gatekeeper behavior (expected — pending macOS confirmation #2256): Children are ad-hoc signed and never notarized. With quarantine stripped by the assembler, a locally-generated bundle is expected to launch with no Gatekeeper prompt (or a one-time right-click → Open if one appears). This behavior has not been confirmed on a real macOS host — see Troubleshooting if you encounter a prompt.

Troubleshooting

Gatekeeper prompt after generation

Children are ad-hoc signed and never notarized. The assembler strips the quarantine xattr, so locally-generated bundles are expected to launch without a Gatekeeper prompt. If you do see one:

xattr -dr com.apple.quarantine ~/Applications/{name}.app

Replace {name} with the LEAF app's name (modmsg, myapp, etc.). After this, double-clicking the .app should open it normally.

"shared core not found" error on launch

If a LEAF exits immediately with a "shared core not found — regenerate this app from the manager" message, ROOT has been moved, renamed, or deleted since the LEAF was generated. The LEAF's core-path sidecar still points to the old ROOT location.

Fix: reinstall ROOT at the same path it was at when you generated the LEAF, or open ROOT from its new location and re-generate the LEAF app.

There is no automatic relocation or recovery UI — the LEAF must be re-generated.

The LEAF app opens onboarding instead of my workspace

Each LEAF's workspace binding lives in ~/.config/zudotext/{name}/config.json (schema v2, {"workspace":{"id":…}}, epic #4204 D2). If this file is missing, unreadable, malformed, or in the retired v1 shape ({"workspace":…}), the LEAF resolves as unbound and boots to onboarding rather than opening a specific workspace — there is no local-directory fallback. From onboarding you can attach the existing workspace (or create a new one); the binding is written only after a successful unlock.

Updating ROOT and keeping LEAFs current

When ROOT is updated in place at the same path, all existing LEAFs pick up the new core automatically on their next launch — no re-generation needed. This is the expected update workflow.

If you moved ROOT to a different path (or macOS updated it to a new location), existing LEAFs will fail to launch. Re-generate them from the new ROOT location.

Sizes and generation times

MetricApproximate value
zudotext.app installed size<TBD: macOS verification — please measure /Applications/zudotext.app with du -sh and fill in>
Per-LEAF stub sizeKB-scale (stub + sidecar only; no copy of the core)
Per-LEAF assembly timeSeconds (stamp stub + sign; no compile)

Each LEAF is a KB-scale stub — it contains only the stub binary, Info.plist, icon, and the core-path sidecar. The shared core (~80 MB) lives once in ROOT's bundle and is never duplicated.