Namespace tree web view — org-owned structure management (Slice 1.5, part 1)
Namespace tree web view — org-owned structure management (Slice 1.5, part 1)
Created: Fri 26 Jun 2026 16:42:25 CEST Updated: Fri 26 Jun 2026 16:42:25 CEST - Initial design from brainstorming session Document Version: 1.0 - Initial design from brainstorming session Security Classification: Internal Technical Documentation Target Audience: Frontend Engineer, API Engineer, Product (Paul) Author: Paul Wisén
Problem
Slice 1 built the namespace registry backend (tables + GraphQL mutations) so the DB is the source of
truth for the issuer tree and path allocation. But there is no surface for a person to build their
tree — the createNamespaceNode / namespaceTree mutations are live but unreachable from any UI.
Paul needs to build a category tree (e.g. Kartonger, Påsar) so he can then generate tags for
those physical objects.
This spec covers Slice 1.5 part 1: a web view in the existing Plings-Web app to build/name/browse an organization’s namespace tree. Part 2 — keygen ↔ registry generation (reserve → derive locally → confirm, which actually mints tags) is a separate follow-up spec; it is gated on a CLI-auth decision and is out of scope here.
Key decision: the organization owns the structure
The namespace tree is owned by an organization, not a user. The model is organization ↔
manufacturer (1:1 per wallet): registering an org as an issuer assigns it the next
manufacturer_index. Individual users can still own sub-branches within the org’s tree
(namespace_node.owner_user_id), preserving the two-tier model of ADR-0050. Manufacturer-index
assignment stays Plings-governed (ADR-0050) — it is not a self-service action in this slice.
Concrete setup for Paul (one-time, performed by an admin / via MCP, not the UI)
- Create the dev wallet row.
wallet_versionsonly has v1; Paul mints under wallet 3 (dev). Insertversion_id=3(version_name='v3',environment='development',is_default=false,master_key_id= the dev key ref). The registry tables FKwallet_version → wallet_versions, so this row must exist before any wallet-3 node. - Register org “Pauls” as a manufacturer at wallet 3 → index 2 (Plings seed holds index 1).
Sets
manufacturer_registry.organization_id= the Pauls org id (9ba2023b-117f-4346-a809-839a66b8c0d4). Paul’s namespace root becomes3.2, his nodes3.2.x. (Org “Plings” → existing index 1 is a separate, later step.)
Wallet 3 is the dev key (disposable per ADR — production starts clean under wallet 1 at go-live). Tags minted now are for testing the full flow and may be reprinted at go-live.
Backend (Plings-API) — one small read query
Everything else reuses Slice 1. The only addition:
organizationManufacturer(organizationId: ID!, walletVersion: Int!): ManufacturerInforeturning{ walletVersion: Int!, manufacturerIndex: Int!, name: String! }ornullwhen the org is not registered as a manufacturer for that wallet. Readsmanufacturer_registryby(organization_id, wallet_version). Auth: the caller must be a member of the org (mirrors theorganization_memberscheck used elsewhere). This lets the frontend resolve the(walletVersion, manufacturerIndex)pair that scopes every tree call.
Reused as-is from Slice 1: namespaceTree(walletVersion, manufacturerIndex),
createNamespaceNode, renameNamespaceNode, retireNamespaceNode.
Frontend (Plings-Web)
Placement & routing
- New protected route
/organizations/:orgId/namespace, reached from each Organization card on the dashboard (a “Tag structure” / “Namespace” action). Scoped to that org and the dev wallet (3) (hardcoded for this slice; wallet switching is future). - Follows the existing routing/auth conventions (
react-router, session-validated; the ApolloauthLinkattaches the Supabase JWT automatically — no manual auth work).
Data layer
- A
useNamespaceTree(orgId)hook: first resolves the org’s manufacturer viaorganizationManufacturer, then queriesnamespaceTree(walletVersion=3, manufacturerIndex). GraphQL documents go insrc/graphql/{queries,mutations}.ts; types insrc/types/graphql.ts(run codegen after the API schema change). MutationsrefetchQueriesthe tree (mirrorsuseMyOrganizations). - If
organizationManufacturerisnull, the page shows an explanatory empty state (“This organization isn’t set up as a tag issuer yet — contact Plings”) rather than a register button (registration is Plings-governed; deferred to an admin surface).
UI
- Container
Card(matchingOrganizationsSection), title from the org name. - The tree is rendered recursively from the flat
namespaceTreeresult (each node hasprefixDecimal,parentNodeId,levelIndex,name) using shadcn Collapsible/Accordion +lucide-reactchevrons. Each node shows itsnameandprefixDecimal(e.g.3.2.1), with per-node Add child, Rename, Retire actions (aDialogfor add/rename, anAlertDialogconfirm for retire). A top-level Add root node adds directly under the manufacturer. - Retired nodes are visually de-emphasized (Badge “retired”), not removed.
Error/empty handling
- Loading →
Skeleton; query error → inline retry (theOrganizationsSectionpattern). createNamespaceNodeenforces depth (≤ 6 category levels) and authz server-side; the UI surfaces the GraphQL error message in the dialog (e.g. depth-cap, not-authorised).
Testing
- API: a resolver test for
organizationManufacturer(member sees the row; non-member → null/denied; no row → null), using the existing fake-engine pattern. Fullpoetry run pytestgreen. - Frontend (no unit runner):
npx tsc --noEmit+npm run buildsucceed. Manual on-device/in-app check: build a tree under “Pauls”, confirm prefixes (3.2.1…), rename, retire, depth-cap error, empty state for an org with no manufacturer.
Out of scope / next
- Slice 1.5 part 2 — keygen ↔ registry generation: reserve → derive locally (master key) → confirm. Needs the CLI-auth decision (paste-token / email+password / file-token). Separate spec.
- Admin manufacturer management (self-service “register my org as an issuer”, wallet switching, org “Plings” → index 1). Deferred; tracked in ROADMAP Backlog.
- Production wallet (1) minting — these dev (wallet 3) tags may be reprinted at go-live.
Decisions to record (ADR, when this lands)
- Organization ↔ manufacturer mapping (the manufacturer tier of ADR-0050 = an organization;
individuals own sub-branches via
owner_user_id). Likely a short ADR refining 0050, written when part 1 + the setup land.