Skip to content

App System

Status: ✅ Decided (grill session, 2026-07-04) — not yet implemented; this page is the contract the implementation workstream builds against (backlog: TODO.md § App layer). Companions: Surface System (the document layer this composes; this page amends its D18/D19), Surface Platform (SUR-1..14), and the vision pages The Everything App and Malleable Software (the L0–L4 authoring ladder apps must serve). Foil: Palantir Foundry (Workshop / Carbon / Marketplace / Object Explorer) — researched 2026-07-04; its validated lessons are cited inline and collected in the foil.

The thesis

Vinxi is an operating system, so it runs many apps: what a site sees depends on which packages its Universe installs, and each app brings its own screens, tools, and shortcuts. An app is the launchable unit — a named, entitled, slug-mounted composition of Surfaces. The layering is strict:

  • Packages install (the distribution + entitlement unit, ADR-0024),
  • apps launch (the navigation + identity unit, this page),
  • Surfaces render (the content unit, Surface System),
  • and one URL grammar addresses all of it: path = identity, query = state.

Apps are documents, not code — the same doctrine as Surfaces. A package ships app documents; a user or partner authors one in the studio; the shell treats both identically.

Decision record

Format: decision → rationale → consequence. All decided 2026-07-04.

A1 · An app is a layer between package and Surface

An app is a first-class object between the package (install unit) and the Surface (content unit): a package provides one or more apps; an app composes Surfaces. It is userland/shell composition declared as data — the kernel never grows an “app” concept beyond the registry documents (D17’s “frontend modules/apps are userland composition” stands). The word is “app”, unhedged: buyers purchase apps, partners build apps, users launch apps; the vision canon already committed (“field workflows are simply the first ‘apps’ that run on an OS-grade shell”). Alternatives rejected: module (retired here, and Workshop’s name for its apps — confusing in competitive conversations), program (kernel-pure but wrong in every buyer-facing sentence), workspace (taken by the IR node).

A2 · URL grammar: path is identity, query is state

/<app-slug>/<surface-slug>/<subpage>/<subpage>…?<dials>
/ → the launcher app
/<app-slug> → the app's declared home Surface

The path answers where you are (app → Surface → drill-in chain); the query answers what state it’s in — the existing dial codec unchanged (f= a= t= l= ar= v= d.<id>=, defaults elide). Consequences:

  • Subpages are promoted into the path. The subpage chain (D18) moves from the page dial (p=a/b/c) to path segments; the resolver’s chain semantics (graceful tail truncation, breadcrumb, deepest-active resolution) are unchanged — only fed from the path. The reserved page dial retires; bare p= leaves the codec.
  • Parallel subpage stacks get the reserved query namespace p.<region>=<chain> — a secondary navigable region (inspector, aside, overlay) with its own independent chain, sharing the Surface’s dials (e.g. ?p.inspector=cable-42/history). Reserved now, designed when the first two-stack screen ships.
  • This resolves the Lexicon’s route-grammar residual completely: one URL grammar for the whole OS, no per-app route tables, no authored routes anywhere.

A3 · Flat app namespace; slug is presentation, id is identity

  • Apps are a flat namespace — exactly one path level, never nested. Structure inside an app is Surfaces + subpages; structure across apps is the launcher. The launcher is a peer app, not a parent.
  • App id (e.g. networkaccess.fiber) is the stable global identity — used by entitlement, references, openIn, config. Slug (e.g. fiber) is the URL mount the app declares, defaulting to the id’s last segment; slugs are globally unique per install (collision = loud install error, same doctrine as duplicate Surfaces). Renames ship an aliases: [] list — old slugs redirect, bookmarks and field-printed QR codes survive. Tenant-side slug overrides (white-label mounts) are a declared install-time extension, deferred; the id, never the slug, is the portable identity.
  • / is the launcher — itself a built-in app (A11), a plain Surface querying installed × entitled apps. Deep links go straight to their app, never bounce through the launcher. A tenant may configure a default landing app (per-capability-group defaults are a noted door, after Foundry’s per-group homepages); the default is the launcher.
  • /<app-slug> renders the app’s declared home Surface directly — no redirect; the clean URL is the app’s home.

A4 · Apps declare rosters; nav derives from roster × capabilities

The relationship inverts D19’s grain: the app composes a roster of Surfaces by reference; Surfaces stay pure content with global ids. Each roster entry carries the app-local slug (what appears in the path) and the nav metadata (label, icon, order, capability gate, form factors). Consequences:

  • Clean app-local URLs/fiber/noc, not /fiber/networkaccess.noc.
  • Surfaces are reusable across apps: a shared map/settings/admin Surface can be rostered by several apps, each assigning its own label and position.
  • modules retire. The cross-package nav bucket (D19’s provides.modules, merged by id) is superseded: the app roster is the nav grouping. In-app “sections” (sidebar sub-grouping inside one app) are a deferred roster extension; v0 rosters are flat.
  • Nav stays derived, never authored — now f(roster × session capabilities) per app, plus the launcher level f(installed apps × capabilities). Capability-gated hiding is unchanged from D19.
  • Cross-app entity navigation stays computed and nearly free: data is one shared graph, apps are views over it, so “Open in Ticketing” is a path change carrying the same app-agnostic f= focus dial. openIn(entityKind) returns (app, surface) pairs, preferring the current app’s roster (the same curation Foundry’s Carbon applies to its “Open In” targets).
  • One undoable user intent = one history entry. App switch, Surface switch, subpage drill-in, opening a parallel region, discrete dial change → pushState; continuous adjustment (viewport pan/zoom) → debounced replaceState. D8’s model, extended to the path.
  • URLs are form-factor-invariant. A deep link encodes logical state; the shell decides rendering (desk shows p.inspector side-by-side, phone collapses it to a pushed sheet; back pops it either way). One link works on every device. The rejected alternative — promoting phone overlays into path segments — buys a purer native back-stack at the cost of non-portable links. (Foundry forks separate mobile modules per app; we refuse the fork entirely.)
  • Cold-start degrades to the deepest valid state, never blank: unknown app slug → launcher, unknown Surface slug → app home, unknown subpage tail → deepest valid prefix (already implemented for chains). Loud at install, graceful at render — now for the whole path.
  • Every app state is canonically URL-addressable by default. This is a competitive claim, not hygiene: Workshop’s deep-linking is opt-in, partial, and load-time-only (“changing the URL after the module has loaded does not dynamically update variable values”), producing wrapper-module folk workarounds. Vinxi’s posture is the direct counter.

A6 · The app document

An app is its own diffable, agent-authorable document — *.app.json — listed by (not inlined in) the package manifest:

{
"app": "networkaccess.fiber", // global id = identity
"type": "surfaces", // discriminant; "code" reserved (A12)
"slug": "fiber", // URL mount; defaults to id's last segment
"label": "Fiber",
"icon": "cable",
"home": "noc", // app-local slug rendered at /fiber
"aliases": [], // prior slugs, redirected (A3)
"commands": [], // reserved: command-bar (⌘K) contributions
"surfaces": [
{
"slug": "noc",
"surface": "networkaccess.noc",
"formFactors": ["desk", "phone"],
"nav": { "label": "Network Ops", "icon": "activity", "order": 1,
"capability": "noc.reroute.approve" }
}
]
}

The package manifest’s provides gains apps and loses modules; the per-surface nav block moves off SurfaceEntry onto roster entries. The roster resolves against the union of all installed Surfaces — an app may roster a Surface another package ships (same no-requires, deps-derived-from-refs doctrine as D17/D19); a roster entry naming an absent Surface fails install loudly.

A7 · Routability and the canonical-app rule

A Surface no app rosters is unroutable — there is no bare-surface URL form (the playground/studio can still open any document). When several apps roster the same Surface: links always carry the explicit app; where the system must choose (openIn fallback, notifications), prefer the app from the Surface’s own package, then roster order.

A8 · Entitlement is installation

“You bought it” means “your Universe installs it.” No subscription/license primitive exists in the kernel or shell: a bundle is a factory-side named set of packages (ADR-0024 — sites pull governed packages), the launcher simply shows installed apps × the session’s capabilities, and per-user differences within a tenant remain capabilities (D7). Enforcement is honest — what you didn’t buy is not latent behind a flag; it isn’t there. In-product upsell/trials, if ever wanted, are a launcher feature fed by a catalog outside the Universe — never a runtime entitlement check.

A9 · Registry-first: apps, Surfaces, and realms are kernel documents

The kernel-side document registry is the design target now: app documents, Surface documents, and realm schemas are registered documents — graph citizens — and install/publish/uninstall are kernel Acts (audit + rollback native; Foundry lacked product uninstall until 2025 — ours is a free consequence). Every document carries an origin: bundled (arrived in a factory package, the files in apps/*/surfaces/ are just the transport the factory replays into the registry) or local (authored in this Universe — the malleable-software path made real). No shell feature may be designed that only works with files. Corollary: installed apps are entities (system.App), so the launcher is a plain Surface querying the graph — no special machinery.

A10 · Vendor documents are immutable; tenant customization is an overlay

A tenant never edits a bundled document. Customization (reorder nav, relabel, add a local Surface to a vendor app’s roster) is a separate overlay document that patches the roster — config, not forks (ADR-0024). This is the direct counter to the foil’s sharpest pain: Foundry upgrades overwrite local edits (“these edits will be overwritten by the content in the new product version”) and unlocking an installation is officially framed as forking. Package upgrades replace the vendor document and re-apply the overlay; overlay conflicts (patched entry no longer exists) degrade gracefully and report loudly. Overlay mechanism design lands with the registry build; the principle is settled.

A11 · Lifecycle: draft → published now; proposals later

Registry documents are draft (author-visible only — their studio, their launcher’s drafts shelf) or published (tenant-visible, modulo nav capabilities). Publishing is one Act; editing a published document creates a draft revision; publish swaps the pointer; prior revisions are retained (Acts make history free). Whether a user may publish at all is one capability — registry.publish — granted narrowly or broadly per tenant. There is deliberately no team-only visibility tier in v0. The upgrade path to review-gated publishing is ADR-0017 proposals (“proposal to publish”) composing on top of this two-state model, never replacing it. Two studio conveniences noted from Workshop’s mature flow: an auto-publish-on-save toggle, and a visual changelog diff.

A12 · The system namespace, and one namespace only

The OS namespace is system — package id system, realm system, app ids system.launcher|studio|console|reality|explorer, kinds system.App, system.Package, system.User, system.Site. The install gate reserves system, sys, and vinxi: non-factory packages claiming any of them (as package id, realm, or app-id prefix) are rejected loudly. sys exists only as a rejected alias, never a live one — two spellings of the OS namespace would be a permanent “is it sys.User or system.User?” tax. This amends the site & identity decision record (2026-07-04, parallel workstream), which named its kernel-native realm sys (sys.Site, sys.User): that design stands in full, spelled system.*. (The kernel’s HTTP route family /sys/act/…, /sys/query/…, /sys/errors is a separate, already-implemented wire convention cited by ADR-0014 — left as-is; renaming it would be an ADR-0014 supersession, not a side effect of this page.)

An app’s type is a closed discriminant: surfaces (a roster, everything above) — and code, reserved: a custom bundle mounted at a slug with the same identity, entitlement, and nav treatment (the OSDK-style escape hatch; first user is our own studio, below). Reserved now so the escape hatch never needs a schema break.

A13 · The built-in apps: the system package

Built-ins are an ordinary package the factory pulls into every Universe — same manifests, registry, Acts; dogfood, no special machinery — with one extra property: non-removable (the factory always includes it). Capability gates do all per-persona hiding.

AppSlugWhat it isv0
system.launcher/The grid/sidebar of entitled apps — a plain Surface over system.App
system.studio/studioThe authoring studio — the playground grown up: edit/draft/publish apps & Surfaces, builder agent. The first type: "code" app
system.console/consoleAdmin: installed packages, registry state, publish rightsthin, read-only
system.reality/realityThe ontology workbench — browse the merged schema snapshot (entity types, traits, links, derivations per realm); later, edit by drafting a new SchemaDocument version installed through the existing Act (schema versions are already immutable kernel-side)read-only browser
system.explorer/explorerCanonical entity viewer — when built, ships a zero-config default view for every entity type, with custom Surface-backed views layered non-destructively on top (the Core-Object-Views pattern)slug reserved

“Reality” is the established canon word for the ontology bundle (realities/, vinxi reality install) — no synonym (datamodel, model) is introduced. The studio-as-app call is deliberate: if the app-maker can’t be expressed as an app, the model has a hole.

Per-user shell state — pinned/recent apps in the sidebar — is a fourth state category (not dials, not install, not capability): per-actor preferences, shell-owned; localStorage in v0, kernel-asserted later.

A14 · Empty-is-loud

Separating app/Surface visibility (capabilities) from data visibility (query results) creates the silent-blank-screen failure mode — bad enough that Workshop ships a dedicated “Check access” diagnostic panel for it. Doctrine here: a shell must always be able to explain why a view is empty. The resolved tree already carries the machinery (the dropped-conditions X-ray); the diagnostic affordance lands in the studio/inspector first, and no shell may render an empty state it cannot account for.

The Palantir foil

Researched 2026-07-04 (Workshop, Slate, Carbon, Marketplace/DevOps, Object Explorer, OSDK — official docs + community threads). What it settled:

Foundry findingVinxi posture
Workshop URL state: opt-in, partial, load-time-only; object sets encode as single RIDs; wrapper-module workarounds for stable URLsEvery app state canonically URL-addressable by default (A2/A5) — the win condition
Carbon workspace = curated module collection, per-group defaults, “Open In” scoped to the workspace’s modulesConvergent with A4 rosters + A3 launcher; per-group default app noted as a door
Marketplace upgrade overwrites local edits; unlock = fork; staff guidance: don’t edit installed resourcesA10 overlays — vendor-immutable + tenant config is the differentiator
Workshop save/publish: versions, revert, changelog diffs, publish-designates-viewer-versionA11 adopts the shape; auto-publish toggle + visual diff noted for the studio
Every object type gets a default view + canonical URL; custom Workshop-backed views layer non-destructivelyA13 system.explorer: zero-config default view per entity type, Surface-backed custom views
Separate mobile modules per app; desktop widgets disabled on mobile; separate mobile launcherRejected — one URL, one document, shell adapts (A5)
Module-ACL vs data-ACL split → silently blank widgets → dedicated “Check access” panelA14 empty-is-loud doctrine
Slate: free-form HTML/JS aged into a decade of sandboxing and soft-deprecation; survives only for public appsVindicates IR-not-code; public apps noted as a door; escape hatch is the governed type: "code" tier (A12), not free-form documents
Ontology collisions across products silently renamed (Truck_1); no overlap tooling; uninstall shipped 2025D17 loud reference validation already prevents; uninstall is an Act from day one (A9)
Marketplace install-time inputs (stub resources, column mapping)The well-designed version of install-time package config — parked door, shape known

Build order

Phase A — schema + router (surface-runtime). App document schema + parse/install gates (provides.apps, roster union-resolution, slug uniqueness, reserved namespaces); path spine /<app>/<surface>/<chain> feeding the existing subpage resolver; page dial retired; launcher + shell nav derived from rosters. Fixtures migrate off modules. Done when: the networkaccess + trafficure fixture packages install as two apps, the launcher lists them, and every golden-fixture state round-trips through the new URL grammar.

Phase B — registry (kernel). system.App/system.Package/document kinds; install/ publish/uninstall Acts; origin bundled|local; draft/published lifecycle + registry.publish; factory replay of package files into the registry; frontend install gate becomes a projection consumer. This is the “file swap, not machinery” moment the manifest schema header promised.

Phase C — the system package. Launcher as a Surface over system.App; playground migrates to system.studio (first type: "code" app); thin system.console; read-only system.reality. Overlay documents (A10) land here with the first real customization ask.

Residuals (parked, not decided)

#ItemRe-entry point
AR1Parallel-region (p.<region>) full designFirst real two-stack screen
AR2In-app roster sections (sidebar sub-grouping)First app whose flat sidebar overflows
AR3Embedded Surfaces with dial mapping (Workshop’s embed-with-interface, Loop)First dashboard-composition ask; relates to R3
AR4Tenant slug overrides / white-label mountsFirst white-label deployment
AR5Public/unauthenticated apps (capability-less session at a slug)First public status-board ask; Slate’s one retained superpower
AR6Per-capability-group default landing appOperator feedback post-launch
AR7Install-time package configuration (inputs/stubs)First package needing site-specific binding at install
AR8commands semantics (⌘K contributions)Command-bar workstream
AR9type: "code" substrate (sandbox, mount contract)system.studio migration forces the minimum; partner code apps set the rest
AR10Team-scoped visibility between draft and publishedADR-0017 proposal flow (A11’s door)
AR11Overlay document mechanism (A10 principle → schema)Phase C, first customization ask

Naming note

App (this page) is the product concept — a launchable composition of Surfaces. The repo’s apps/ directory (apps/network-access, apps/book) contains Vite frontends — build artifacts, not Vinxi apps; the collision is dev-facing only and tolerated. Per the naming canon: Vinxi is the kernel, Lepton the company; the OS namespace is system and sys/vinxi are reserved-rejected (A12).