<!-- https://getkonjo.com/design/studio/ia-and-inventory · source: docs/design/studio/ia-and-inventory.md -->

# Studio — information architecture and screen inventory

What Studio is, how it is organised, and every screen it ships.

> Part of the [Konjo design language](../konjo-design-language.md). The laws in the spine
> apply here unless this chapter contradicts them. Studio-specific — see
> [the dialect](dialect.md).

> **Where the visual rules live.** This chapter carries Studio's structure and vocabulary.
> It does **not** carry colours, type, spacing or radii — those come from
> [the generated token tables](../../../.claude/skills/design/references/tokens.md), and the
> way Studio deliberately differs from mobile is [the dialect](dialect.md).
> Tables get [their own chapter](tables.md); so do [charts](charts.md).
>
> This document previously opened with a section headed *"Visual identity (strict — do not
> deviate)"* that published a hand-maintained colour table. By the time anyone noticed, it was
> still carrying `#FAFAFA` as the page (the 1.044:1 inversion the separation law exists to
> correct), `text.tertiary` at `#888888` (3.54:1) and success text at `#1F8A4C` (3.86:1) —
> three values the shared palette had already fixed as AA failures, under a heading telling
> people not to deviate from them. That section is gone, and values are now generated from
> `packages/design-tokens/tokens.ts` so the same thing cannot happen twice.

## What Konjo is

**Konjo** (根性 — Japanese sports slang for *guts*, fighting spirit) is a martial-arts
platform with two sides: a mobile training app for students and instructors, and
**Konjo Studio**, the desktop web dashboard where dojo owners run the business.
Positioning line: **"Train. Teach. Run the dojo."**

The core loop: students log real training in the mobile app → instructors see
readiness and gaps → the dojo operates from better data in Studio. Konjo is a
martial-arts operating system, not a generic gym CRM — rank ladders, belt testing,
curriculum, and instructor readiness are first-class. The launch community is **Cuong
Nhu** (a karate-based style; its association is the CNMAA), but the platform is
multi-style by design: each dojo can build or adopt its own style with its own belt
ladder.

Competitors (Zen Planner, Kicksite, Gymdesk, MyStudio, Mindbody…) are known for dated
UIs, fee opacity, and shallow reporting. Konjo Studio should feel like the opposite:
modern, calm, transparent, data-dense without clutter.

## What Konjo Studio is

A **Next.js web dashboard at studio.getkonjo.com** for dojo staff. Users sign in with
the same account as the mobile app; students have no access.

Personas, in order of importance:
- **Owner / head instructor** — sees everything: money, roster, risk, reports.
- **Instructor** — reduced view: schedule, attendance, their teaching history, My pay.
- **Front desk** — members, kiosk, shop, leads, messages.
- **App admin (Konjo staff)** — additionally sees Registry and Dojo launches.

Navigation items appear/disappear per role, so screens must stand alone gracefully.
Users with multiple dojos get **dojo switcher chips** near the page title; the active
dojo scopes every screen.

## Information architecture (frozen — keep unless asked)

Left rail, top to bottom. Sub-items indent under their parent. Rows appear per
capability, and this list is the rail as `NAV` in
`apps/studio/src/app/(studio)/layout.tsx` declares it —
`scripts/verify/nav-gate-parity.mjs` fails the build when a row's capability
stops matching what `proxy.ts` demands of its href.

Each section is ordered by **how often a dojo opens the row**, not by the order the
rows were built — the 2026-08-30 close-out reordered Operate and Revenue after the
audit found Team (monthly) and Incidents (rare) holding slots 3–4 while Attendance
sat 6th and Follow-ups 10th.

**Operate**
- Home
- Families *(default household list; “View members” beside the heading opens the roster. Staff without family access see Members instead.)*
- Schedule → Private lessons *(Archived classes is linked from the week grid, not
  the rail — un-archiving is a rare undo and was spending a permanent nav slot)*
- Attendance
- Follow-ups
- Substitutes
- Testing
- Waivers
- Kiosk
- Team → Payroll · Staff & access · Audit history
- My pay *(beside Team: the employee half of what Payroll runs from the employer
  side. It sat under Revenue until the audit found that an instructor-only account
  saw a "Revenue" section header introducing exactly one item — their own pay stub.)*
- Incidents & safety

**Revenue**
- Shop → Catalog · Merch · Merch orders
- Billing → Plans · Family accounts · Payments
- Reports
- Event fees *(prices events and opens public registration; it does not create
  them — events are made in the app, from the floor)*

**Grow**
- Leads
- Messages → Templates · Automations · Scheduled
- Public page

**Community** *(Konjo app-admins only)*
- Registry
- Dojo launches

**Setup**
- Belt ladder
- Settings → Integrations

## Screen inventory (the complete shipped surface)

### Operate

**Home** — Daily action queue. Home is the **documented exception** to the page-header
contract at the bottom of this file, and carries two lines, not three: the eyebrow is the
**dojo and the clock** (`KONJO AUTOMATED TESTS · SUNDAY · AUGUST 30, 2026 · 5:31 AM DOJO
TIME`), the title is the **status line** counting what is open (`3 things need you today,
Playwright.`), and there is **no page-sub** — the queue underneath is the sub. See "Home is
the one exception" below before changing either line; the dojo belongs in the eyebrow, never
in the h1. Then dojo chips if the person has more than one, then six snapshot cards, each
with a "→" link into its section. **No greeting** — this said "Greeting" until round 8 read
it against the header convention below and against [voice](../voice/voice.md), which rules
out first person and cheerfulness. "Good morning, Galen!" is the app talking about itself on
a screen whose job is to say what needs doing. The sections are: **Money** (collected this month, past-due list),
**Today** (today's classes with booked counts, open sub requests), **Needs attention**
(at-risk students with reasons), **Growth** (new leads, join requests, trials),
**Testing** (test-ready count, next test, recent promotions), **Birthdays** (next 30
days).

**Members** — The alternate view within Families, reached with “View members” beside the heading. “View families” switches back; the Families rail row stays active on both routes. Staff without family access retain a Members rail row and see no family switch. The roster table: member (avatar, name, and a sub-line carrying
email + their family name, which links to that household), belt dot + label, role,
billing pill, last attended (with "21d+ absent" flag), joined. Members with no login
yet carry a small `managed` flag beside their name. Search, billing-status filter
chips, and the table share one bordered panel. CSV export, a heading switch to Families, and links to
join requests, Add member, Import CSV. **Reveal paging, no round-trip** — the whole
roster preloads and filters client-side and renders 25 rows at a time behind
`Show 25 more` / `Show all N`, per [tables](tables.md#density-and-volume).

**Families** — The primary directory and sidebar destination. A single “View members” button beside the title opens the individual roster, keeping the selected dojo. Clicking Families in the rail always opens the household list. The household index, and the only list of them: family (member
count, distinguishing label, short code), members, payer, owed, billing roll-up
pill; the row opens the family workspace. Same Reveal paging as the roster;
search matches a family, a student, a payer or a short code. The payer and money
columns are **dropped, not blanked**, for a viewer without the capability that
owns them — a dash would read as "owes nothing". `/members?view=families`, the
tabbed grid this replaced, redirects here.

**Member detail** — The CRM record. Header (avatar, belt, role, membership).
Two-column body — left: billing summary card (plan, status, renewal, card on file,
actions: checkout link, freeze/unfreeze, assign plan), payments table, staff notes,
lifecycle timeline; right: contact info (editable phone/address), emergency contacts,
documents (upload/download), family links (guardian, payer account, managed children).
The Training tab opens on the **Rank record** — every promotion and the corrections and
voids that superseded them — and, for a head instructor or owner, a closed `Correct or
void a rank` disclosure holding the two controls that can take a rank back. **Correcting**
names the rank the member should hold; **voiding** takes a record back entirely and
returns them to the rank that preceded it, which the RPC resolves so nobody types it. Both
write a superseding record and never delete the one they replace; a voided promotion stays
on the list marked `Voided`, a corrected one marked `Superseded`. Correcting is always
offered; **voiding is offered only where the RPC would accept one** — the standing record
is not itself a void, the database can name the rank that preceded it, and that record
conferred the rank the member holds now. Where it is not offered, a line in the disclosure
says which of the three is missing and sends the person to Correct instead.

**Join requests** — Pending self-signups claiming a role/belt at this dojo; approve or
decline per row.

**Follow-ups** (`/tasks`) — The things that need a person rather than an email: raised
by automations, raised by attendance, or assigned by a colleague. Assigned to me,
the team's open list, snoozed and completed. Home's **Needs your call** queue is the
*ranked, today-shaped* view over the same population; where a follow-up already holds
a student, Home's row for them says who holds it and opens `/tasks?subject=<id>`
rather than starting a second conversation.

**Team** — Staff hub with tabs Overview / Payroll / Staff & access / Audit history.
Overview: stat cards (active staff, classes taught 90d, est. hours) + a card per staff
member (role pills, teaching stats, recent classes, pay summary). Per-instructor
teaching-history page with export.

**Payroll** — Employer setup, workers & compensation rates, pay schedules, pay runs
(lines by type, approve → lock → export CSV), discrepancy resolution.

**Staff & access** — Invite staff by email with role checkboxes (Instructor / Head
instructor / Front desk), current-staff list with per-row role editing, mark departed,
owner-change proposals with per-owner voting.

**Audit history** — Append-only log table: when, record, change, actor, source.

**Incidents & safety** — Incident queue (status + severity filters) → report form
(category, severity, narrative, people involved) → detail page with review workflow
(submit → review → close), follow-up tasks, safety/accommodation flags, evidence
uploads, external-notification log, print/PDF.

**Schedule** — Week grid of recurring classes × 7 days with booked counts, prev/next
week. Class-instance page: roster with check-ins, cancel-class-and-notify, book a
member, waitlist with promote.

Per-class booking (toggle, capacity, cancellation window, trial capacity, default
instructor) lives on **each class's Settings tab**, alongside the name, day, time and
eligibility it belongs with. The standalone "Booking settings" screen this file used
to document was retired 2026-08-03 and its route is a 308 to `/schedule`.

**Archived classes** (`/schedule/archived`) — Classes taken off the week grid, with
un-archive. Reached from the week grid, not the rail.

**Private lessons** — Offerings (title, instructor, duration, price), recorded
bookings with status. (Payments for these are informational only today.)

**Attendance** — Retention screen. "Needs attention" risk queue: student, risk-score
pill, reasons, last attended, actions Message / Snooze / Mark away. Away & snoozed
table, all-students table with search, attendance by class. Per-student attendance
detail with stat cards.

**Substitutes** — Open coverage requests (assign an instructor, escalation flag),
covered list, per-class instructor roster with teach/assist roles.

**Kiosk** — Front-desk check-in devices: create kiosk → one-time QR + link reveal,
device table with last-seen, rename, revoke, regenerate link. (The kiosk itself is a
public tap-your-name check-in board.)

**Waivers** — Templates table (versioned; editing bumps version and outdates
signatures) + a **signature matrix** of students × active waivers with per-cell status
(Signed / Outdated / Link sent / Missing) and send-link actions.

**Testing** — Schedule a belt test, per-test candidate table (current belt → testing
for, pass/fail/absent result controls, a **Fee** pill per candidate — Paid / Processing /
Unpaid, shown only for a test that has a fee — and a bulk **Remind N unpaid** that counts
only the families it can actually reach, finalize → bulk promote), test-readiness table
(requirements met per student), **recent rank records** (promotions and the corrections
and voids that superseded them; a corrected or voided promotion stays on the list,
marked). Correcting and voiding are done on the member record, not here — see Members
below.

### Revenue

**Billing** — Overview: Stripe onboarding CTA when unconnected; stat cards (active,
trial, past due, frozen, collected this month), dunning queue (owed, attempts, next
retry, invoice link), recent payments (with ACH "bank rail" badge), payouts card.

**Plans** — Membership plans: recurring (interval, trial days) or class packs
(credits); price edits, subscriber counts, deactivate/reactivate.

**Family accounts** — Payer accounts holding multiple students: expandable cards with
member list, add student, create managed child (no login), checkout links, billing
portal, archive.

**Shop register** (`/pos`) — POS: product grid → cart with quantity steppers → buyer
picker → checkout as cash, payment link (QR, 30-min expiry), charge card on file, or
comp. "Sales today" KPI.

**Catalog** — Product CRUD: price, SKU, stock with low-stock threshold, inline stock
adjust.

**Payments** (`/billing/payments`) — The one money ledger, per D-29: subscription
charges and register sales in one list with their tender, line items, status tabs
(All/Paid/Pending/Cancelled/Refunded), void / cancel-link / refund, CSV export.
`/pos/sales` redirects here — there is no second sales ledger.

**Event fees** (`/event-fees`) — Upcoming events with their fee (belt tests use the
testing fee), public registration toggle + slug + capacity, registrations list per
event with paid status. **Studio prices events; it does not create them** — every
write path for an event lives in the app, so the empty state is a direction rather
than a button.

**My pay** (`/my-pay`) — Any staff member's own approved pay runs: period, hours, gross, line
detail, report-a-discrepancy.

**Reports** — Eight tabs: Revenue · Growth · Attendance · Retention · Churn risk ·
Readiness · Lifetime value · Staff. Date-range presets (30d / 3mo / 12mo / YTD), KPI
cards, minimal SVG charts (monthly revenue, growth trend, attendance trend, belt
distribution), detail tables, CSV export per tab. The toolbar is also the index of
the other money exports — **QuickBooks export** (`/reports/quickbooks`, a one-dojo
journal-entry CSV, not a rail row) and the **Payment ledger** — because "export it
for my accountant" is one job and Reports is where an owner opens it.

### Grow

**Leads** — Kanban board with six columns: New · Contacted · Trial scheduled · Trial
completed · Converted · Lost. Drag-and-drop cards (name, email, age bracket, "Booked
in app" badge). A right-side **lead drawer** opens per lead: schedule trial, assign
staff, convert to member / create account & convert, send checkout link, notes,
activity timeline. Copyable public trial-form URL at top.

**Messages** — Email delivery log (last 200: subject, channel, status, error,
recipient). **Compose**: channel toggle (Email/SMS), audience picker (all members / by
class / by belt range / parents of minors / custom selection), merge tags
(`{{first_name}}`), preview recipient count, schedule or send now. **Templates**
(dojo + read-only Konjo defaults). **Automations**: toggles for birthday greeting,
win-back nudge, trial-ending-soon, cancellation save.

**Public page** — Editor for the dojo's public web page: publish toggle, description,
contact, hero photo, per-instructor show/hide. Links to the live page.

### Community (app-admin only)

**Registry** — CNMAA org-wide member list across all dojos: search, dues filter,
member-number assignment, dues status, CSV export.

**Dojo launches** — Interest queue → launch cases: evidence verification checkboxes,
7-section checklist review, import proposals, final launch control.

### Setup

**Belt ladder** (`/style`) — Style builder: create or adopt a martial-arts style; ordered
belt-ladder editor with colors/stripes; promotion pacing (expected days per rank);
curriculum by rank with quick-add (name, rank, category, plus an optional video link and
description). Both counts in the curriculum table are links: **Test requirements** opens
that rank's checklist in place — offered for every non-entry rank on **any** ladder, not
only the nineteen Cuong Nhu slugs — and **Your additions** opens Your curriculum filtered
to that rank. Rail label, page title and route gate all say the same thing
(`curriculum.manage`); the row lives in `NAV` like every other, and
`scripts/verify/nav-gate-parity.mjs` fails the build if it drifts from `proxy.ts` again.

**Your curriculum** (`/style/curriculum`) — the techniques a dojo wrote, which until now
could be created from Studio and then only ever edited in the mobile app. One panel:
rank filter, then a six-column table (technique, rank, category, video, order, actions)
with reveal at 25 rows. Editing opens a panel at `?item=<id>` with six fields — name,
rank, category, group, description, video link — and saves in place, so a refusal keeps
the typing; a stale-version refusal (someone else edited the same technique) offers a
reload beside the retry. Archive arms then fires and states what it costs: past grades and
plans keep the technique, this dojo's belt tests lose it, and restoring does not put the
test requirements back. No rail row — it is reached from Belt ladder, which is the page
that gives it its meaning. Route gate is `/style`'s (`curriculum.manage`), so no new
prefix in `proxy.ts`.

**Settings** — Stripe Connect card (onboard, status flags, open dashboard), location &
discovery, free-trial allowance. **Integrations**: webhook endpoints (events like
`member.joined`, `payment.created`; pause/test/rotate/delete), delivery log, API keys.
**Costs & profit goal** (`/settings/expenses`, linked from Settings, not a rail row):
monthly running costs and the owner's target draw, which is what Home's business-health
card measures the month against.

### Public pages (no rail — lighter, centered layouts)

These are session-less pages reached by members of the public, not staff. They share
the palette but drop the dashboard shell:

- **Public dojo page** (`/d/slug`) — hero with dojo name + "Try a free class" CTA,
  about, weekly schedule, instructor grid, events.
- **Trial request form** — the lead-capture form.
- **Waiver signing** — waiver text + draw-your-signature pad, token-gated.
- **Event registration** (`/e/slug`) — fee, capacity, spots left; pays via Stripe.
- **Kiosk board** — full-screen tap-your-name check-in for a tablet at the front desk.
- **Unsubscribe / payment thanks / cancelled** — small confirmation pages.

## Recurring UI patterns (reuse these; don't invent parallel ones)

- **Snapshot cards** with a title, a few key numbers/rows, and a "→" link to the
  full section.
- **Stat/KPI card rows** — label, big value, small hint line.
- **Tables** with: sub-text under primary cell, status pills, right-aligned row
  actions, one of the two sanctioned paging shapes (Reveal — `Show 25 more` — for a
  preloaded collection, cursor — `Next 50 →` — for an unbounded one), and a matching
  empty state. See [tables](tables.md#density-and-volume).
- **Inline forms in cards** — label-over-input grids, primary red button, secondary
  bordered button, small inline buttons for row-level actions.
- **Dojo switcher chips** at the top of pages for multi-dojo users.
- **In-page tab bars** (Reports, Team, Student history) — underline or pill tabs under
  the page title.
- **Member search pickers** — debounced search input with dropdown results.
- **Copy-link boxes** — monospace URL in a bordered box with a Copy button (checkout
  links, kiosk links, trial-form URL).
- **Notice/error banners** at the top of the content area after actions.
- **Eyebrow + page title + one-line sub** as the standard page header.

### The page header, exactly

Three lines, in this order, and the first two have a contract.

**Eyebrow = `<section anchor> · <scope>`.** One form, everywhere.

- The **section anchor** is the rail row this page hangs from: its own row when the
  page *is* a rail destination (`Members · Blue Heron`, plain text), the parent row
  when the page sits beneath one (`Members · Blue Heron` on `/members/new`, with
  *Members* a link back). It is the rail's word verbatim, so the row you clicked and
  the page you landed on introduce themselves with the same name.
- The **scope** is whose data is on screen — the dojo. A page that is not dojo-scoped
  names the scope it does have instead: `My pay · Private to Galen Jauss`,
  `Registry · CNMAA`, `Dojo launches · App admin`.

**Home is the one exception, and it is the whole header that is excepted** — eyebrow,
title and sub, not the eyebrow alone. Its eyebrow is the dojo and the clock (`BLUE
HERON · SUNDAY · AUGUST 30, 2026 · 4:21 PM DOJO TIME`), because the page you land on is
the one page you cannot be lost on, and the day and the time are what its queue is
measured against; its title is the **status line** (`3 things need you today,
Playwright.`), which is the one h1 in Studio that replaces its rail noun instead of
carrying it — "Home" names a destination, and a person already standing on it needs the
count, not the name; and it has **no sub**, because the queue directly underneath is the
sub. Read this paragraph together with the title law below: Home is the single row that
law does not reach.

This replaced eight dialects the 2026-08 audit found shipping at once (bare dojo name,
`SECTION · page`, `← ATTENDANCE` back-links, `FAMILY 3DD8EB`, none at all). The rail
is the only place the Operate/Revenue/Grow grouping is stated, and it collapses behind
a toggle on a narrow viewport — the eyebrow is what carries that grouping onto the page
itself. It is 11/800/+1.2 caps like every other eyebrow, and it is never red.

**The page title carries the rail row's noun.** Most rows repeat it exactly — `Members`
→ "Members", `Payroll` → "Payroll", `Belt ladder` → "Belt ladder". A title may *qualify*
it, with a modifier in front (`Schedule` → "Week schedule", `Kiosk` → "Front-desk
kiosk", `Registry` → "Style registry", `Plans` → "Membership plans") or a clarifier
after (`Shop` → "Shop register", `Scheduled` → "Scheduled — what's about to send",
`Event fees` → "Event fees & public registration"). It may not *replace* it: a reader
should never have to learn that Shop is where Register lives, which is what the bare
title "Register" made them do. **`Home` is the one row this law does not reach** — its
title is the status line, per the exception above; 37 of the 38 rail rows satisfy the
law, and Home is the 38th by design.

Two titles carry the noun in a different form rather than verbatim, and are the outer
edge of the rule rather than exceptions to it: `Payments` → "Payment history", and the
`Merch orders` child → "Print-on-demand orders", where the parent row `Merch` one line
up is what "print-on-demand" is naming.

### URL parameters

`?dojo=` is the disciplined convention the codebase already keeps — 50-odd pages carry
it and `NavLink` forwards it on every rail click, so a rail click never silently
switches a multi-dojo user's tenant. Two more, in the same spirit:

- **`?tab=`** names an in-page tab. It is what the member record, the family
  workspace, the register and the class detail already use. Reports (`?report=`) and
  the home dashboard (`?metric=`) predate the rule and still answer to their own
  names; new tabbed surfaces use `?tab=`.
- **Filters keep their own names** (`?status=`, `?range=`, `?dues=`, `?billing=`,
  `?subject=`) because a filter is not a tab — the distinction is worth the two
  vocabularies. What is not allowed is a cryptic one: a parameter a colleague cannot
  read out loud over the phone.

## Domain vocabulary (use these words, not gym-generic ones)

- **Dojo** (never "gym", "studio location", "box"). Staff roles: owner, head
  instructor, instructor, front desk.
- **Belt ladder (Cuong Nhu default, 19 ranks):** White → One/Two Green Stripes → Green
  → One/Two Brown Stripes → Brown → One/Two Black Stripes → Black → Shodan → Nidan →
  Sandan → Yondan → Godan → Rokudan → Shichidan → Hachidan → Kudan. Other styles
  define their own ladders — never hardcode yellow/orange/purple belts.
- **Testing / promotion** — belt tests are scheduled events with candidates,
  requirements ("test readiness"), and finalization that promotes in bulk.
- **Dunning** — the past-due retry queue in billing.
- **Family account** — one payer covering multiple students; **managed child** — a
  student profile with no login, managed by a guardian.
- **Lead → trial → member** — the growth funnel.
- **Waiver** — versioned liability document; signatures go stale when the text changes.
- **Kiosk** — front-desk self-check-in tablet.
- **Substitutes / coverage** — filling in when an instructor can't teach.
- **Registry** — the style association's (CNMAA) org-wide member and dues list.

## Hard boundaries — never design these into Studio

The **mobile app** (separate product) owns everything a student or an instructor does
*on the training floor*: training logs, technique curriculum library, social feed,
events browsing, coaching, achievements, class-mode attendance taking, class plans,
student-side booking and payments. Studio is the *desk*, mobile is the *floor*. If a
request seems to need a floor tool, flag it instead of designing it.

Also out of scope: the marketing website (getkonjo.com — a separate Next.js static-export
app at [apps/site/](../../../apps/site/README.md)), native mobile screens, dark mode, and any
signup/onboarding flow for students.

## Planned but NOT built (design only when a request explicitly asks)

- Third-party integrations depth (Zapier, accounting/marketing sync)
- SMS as a mature channel (email is primary today; SMS is nascent)
- Richer parent communications
- Deeper lifetime-value and churn-prediction analytics
- Private-lesson payment collection
- Per-style curriculum schemas and terminology
- AI front-desk expansion (a staff AI assistant drawer exists; treat it as out of
  scope for redesigns unless named)
- Governing-body registry sync beyond CNMAA
- Multi-currency / i18n

## How to respond to a redesign request

1. Redesign **only the named screen**, at desktop width, with the dark rail visible
   and the correct nav item active.
2. Preserve the screen's data and actions unless the request says otherwise. You may
   reorganize layout, grouping, hierarchy, and flow freely — that's the job.
3. Stay strictly on the palette and design law above.
4. Fill it with Blue Heron Martial Arts demo content — real-looking names, belts,
   dates, dollar amounts, and at least one non-happy-path state (past due, at-risk,
   empty state) so the design proves it handles them.
5. If you're unsure whether something exists in the product, it's in this brief or it
   doesn't exist — say what you assumed rather than inventing new scope.
