Skip to content

Owner review 52 — the three build-order audits ​

Status: Reference · Date: 2026-11-01

The round-52 record (owner-review-52-synthesis-2026-10-31.md § 6) named ten audits before a page counts as implemented, three of which decide the build order: kit → component parity (1), the data contract per page (2) and navigation parity (5). The owner said "run them" (2026-11-01). Three read-only audits ran against HEAD 191e628bd (boards, kit, registries, packages/ui, packages/app, packages/features, migrations, generated types, edge functions); nothing was executed, so a control behind a role or entitlement gate counts as built when its code is there. The full tables are kept beside this record:

This page holds the results, the build order they imply, what was corrected and gated at once, and the decisions left to the owner.

1. Kit → component parity ​

138 public Fvk* pieces (168 functions, minus 30 board scaffolding or specimens):

StatusCountMeaning
exists7same job, props cover the kit's: FvkChoice, FvkTag, FvkChoiceWrap, FvkFlag, FvkLiveTag, FvkBreak, FvkInfoHint
partial14a v2 or near-v2 component with named missing props or variants (SectionHead, PersonRow, ChoiceSet, ValueSheet, Scrim, Meta, FeedEnd, MatchTag, Icon, LevelBand, Avatar, CountTag, Dot, LineSlot)
legacy84the app has the job on the pre-flat grammar; the library's "Replaces in code" names it as replaced
missing31no component, or only an inline implementation (Block, SheetFooter, Fab, FeedItemSkeleton, EditGuard, AdminSheet, DoorPill, StickyEdge, AnswerPair, HelperLine, TrustMeter, Bracket, GroupTable, …)
unsure2FvkGroupTitle (a real Sheet group title, not scaffolding), FvkCellLine

Build order by uses on the six page boards (occurrences of <FvkName, dependencies first): FvkCell 242 · FvkSectionHead 216 · FvkEmptyState 187 · FvkHeader 87 · FvkField 61 · FvkThumb 56 · FvkDialog 55 · FvkPersonRow 55 · FvkChoiceSet 49 · FvkCellGroup 48 · FvkNotice 45 · FvkStrip 42 · FvkSkeleton 41 · FvkT 40 · FvkSheet 39 · FvkInput 37 · FvkChoice 36 (done) · FvkOfflineStrip 34 · FvkTag 34 (done) · FvkCtaBand 33 · FvkMenuSheet 33 · FvkMediaRow 31 · FvkValueSheet 27 · FvkBlock 26 · FvkSheetFooter 25.

The sequence the counts and dependencies give: (1) the v2 text roles (FvkT, the eleven FVK_TYPE roles) and Button/IconButton, which every row and footer consumes; (2) Cell with CellGroup (Switch, PickerCell, CheckRow, TaskCell and the consent row hang off it); (3) SectionHead (the feed variant extended with the Door pill, bulk, note, pager, info) and EmptyState; (4) Header, then Sheet and from it Dialog, MenuSheet, ValueSheet, SheetFooter; (5) the rows (PersonRow, MediaRow, TextRow, FeedItem) on Thumb, Avatar and Meta; (6) Block, Strip, Notice, Skeleton and the form controls with their pages. Pieces drawn only inside other kit pieces (Avatar, Badged, CountTag, LineSlot, LevelTag, …) build with their host; pieces drawn only on library or flow boards (Chart, EditSheet, LiveRow) build with the page that adopts them.

Contradictions between the app and the record (each needs a ruling or a fix before the piece is built):

  1. SectionHeader variant="feed" carries a command prop (tonal sm Button, "모두 읽음"); review 48 removed a command from the head, and the kit offers bulk instead. The prop goes when SectionHead is rebuilt.
  2. FeedEnd draws an end line; review 48 (2026-10-28) ruled the line out, and the kit FvkFeedEnd takes doors only. FeedList and GroupedFeedList do not mount a Feed end yet (hasMore is unread).
  3. ExpandToggle is a chevron-only row (owner, 2026-09-28: "stay away from text-based control buttons"); the kit FvkExpandToggle is a full-width tonal md Button stating the count (review 48). The later ruling stands per the library ("the kit replaces the app component"); the owner may confirm.
  4. cell-row.tsx (CellRow) is not the kit Cell: it is the equal-column run the library folds into Strip. The app has no Cell; FactRow, ListRow settings uses, PickerRow, EditFieldRow and EloFactRow are what the new Cell replaces.
  5. FvkSelectBar is retired (Select mode retired, 2026-10-25) and can leave the kit; FvkFieldNote and FvkOgCard are drawn on no board.

2. The data contract per page ​

82 pages: covered 54 · partial 23 · missing 5 (L0-03 이메일 가입 needs signUp on AuthServicePort; L3-08 테니스 여정; L4-14 회비 설정; L4-16 연결된 계정; L3-46 장부). Board hook names that differ from the shipped names (useConsents/useUserConsents, useAddThreadMembers/useAddThreadParticipants, deriveMatchResultState/deriveMatchResult, useClubByInviteCode) are noted as unsure, not counted as gaps.

Distinct gaps, by kind (the page codes name who needs them):

KindGapPages
new tablematch_notes (match_id, author_id, mood good/ok/bad, body ≤ 500; own-row RLS)L3-08, L3-38
new tableclub_dues_plans (name, amount, cadence incl. one_time/every_n_months/per_session, due rule, grace, proration, applies-to role or 조); dues has only month/yearL2-01, L3-11, L4-14
new tablethe club ledger (opening balance, dated entries with 분류 and 영수증; a confirmed 회비 = one income entry), an aggregate read (balance, month spend), a CSV exportL3-11, L3-46
new tableattendance excuse requests (one per attendance_record, reason ≤ 200, answered by host/총무, 거절됨); today only the official's one-way excuse_attendance_recordL3-01
new tablethe join-vote store for the 멤버 투표 admission rule (votes, deadline, objection) with a cast-vote RPCL3-32
new tableper-user hidden 소식 ids and per-club 소식 muteL1-01
new tablethe identity verification store (CI hash unique, real name, birth date, phone, foreigner flag, verified_at)L0-04
new columnrsvps.leaves_at, available_until_round, checked_in_at, checked_out_at (today: arrives_at, available_from_round)L2-02, L3-01, L3-36
new columndues.plan_id, period_start, period_end; a reminder log (who/when) on dues and session_paymentsL3-11, L4-14
new columnmatches.stage, group, scheduled_start (tournament matches live in tournaments.state JSONB today)L3-38, L3-39
new columnprofiles 주 종목 and 선호 시간 (no editor among the 82 pages sets them)L2-06
new columnthe saved generation preset (소셜/실력별/고정/랜덤) and the policy axes the page offers (pool, 만드는 시점, 확정 시점); GenerationPolicy is spine-only, nothing persistsL3-47
RPCthe trust record read (신뢰 점수 = 50 + 12-month points, 0..100, none before 5 recorded sessions; per-record points, rule, venue/day, streak, praise); only trust_tier existsL1-05, L2-06, L3-01, L3-25
RPCvisibility-filtered reads of another person (match history, achievements, play facts, last-20 attendance + praise); useMatchHistory/useAchievements read the viewer's ownL2-06, L3-04
RPCget_public_share_preview extension (fee, approval mode, isPickup, district; match venue/format/day; NTRP + 신뢰 점수 instead of the raw tier key; club region as words); an anonymous, minimal, rate-limited invite readerL0-06, L0-07
RPCdues reminders (one member / all 미납) and a session-fee reminder, logged; generate_dues_reminders was dropped in 00405L3-11, L3-35
RPCa period parameter (전체/이번 주/이번 달) on the leaderboard readsL3-17
RPCsubmit_match_score expected-revision parameter and the typed refusal score_changed (matches.score_revision exists)L3-38
RPCtournament matches moved into matches with the group-table, bracket and advance reads; retire tournaments.state and its three hooksL3-38, L3-39, L3-47
hookuseHomeNewsFeed (일정/결과/게시글 across the viewer's clubs with the hide filter); bulk pending join requests for the 클럽 list; useTrustRecord; the arrival/exit stamp mutation; a venue's public 매치, home clubs and the owner's waiting count; useMatchNotes + save; useDeclineChallenge; the 정산 나누기 write; identity link/unlink on AuthServicePortL1-01, L1-02, L1-05, L2-02, L2-04, L3-08, L3-10, L3-35, L3-36, L3-38, L4-16
edge functionverify-portone-identity (PortOne v2 identity check: age, CI, duplicate); the AI 요약 job for 테니스 여정 with an entitlement key for the player planL0-04, L3-08
configsupabase/config.toml configures only [auth.external.apple]; Kakao, Naver and Google are not configured locally; enable_manual_linking = falseL0-01, L4-16

Stale board text found on the way: the 매치업 board still asks for player_matchups_by_format, which already exists. The schema work groups into three batches the pages can wait on: notes and trust (match_notes, the trust read, the other-person reads), money (dues plans and periods, the ledger, reminders), sessions (rsvp stamps, match stage/group/schedule, the generation preset).

3. Navigation parity ​

329 drawn links, each resolved to the screen that serves its from page and that screen's import closure:

ClassCountNote
built15227 through a different control than the one drawn (a header icon for a FAB, a CTA band for a MenuSheet, …)
built via another route38the pair opens, but through a screen no page claims (/clubs/[clubId]/admin, /directory-venues/[id])
not built102
to is new20the target page has no route yet
from is new16L0-01/02/03/05/07 have no route; sign-in is LoginScreen inside AuthGate
unsure1link 257, L4-01 → L4-07 from the venue search takeover

Built but not drawn: 52 pushes (12 dropped on purpose by review 52 § 3, 5 moved by the boards to another page or control, 4 not a page pair, 31 unsure: a missing link, or a path the boards never mention, such as the recap card on 홈 that opens 기간 기록). Route helpers no page claims: 22, four with no caller at all (routes.ranking, matchHistory, createPost, notificationsAt). Owed pages: none of the five has a route, a helper or a screen; 정기 일정, 조 and 앨범 관리 exist as inline sections of 클럽 설정, 행사 nowhere.

Findings, in the order they matter:

  1. The club 관리 gear sheet is a routed screen. Every L2-01 gear link to L3-03/07/10/11/25/27/28/29 and L4-20 runs through /clubs/[clubId]/admin (club-detail-screen.tsx:523 → admin-tab.tsx), which review 52 § 3 retires. 신고 관리 (L3-28) is reachable only from that screen's snapshot strip, and only while open reports exist.
  2. L2-04 is served by the wrong screen for navigation. Its claimed route /venues/[id] (VenueDetailScreen) has no navigation but back; every picker, list and signal opens /directory-venues/[id] (routes.directoryVenue, 10 call sites), which no page claims. Of the 22 links touching L2-04: 13 via another route, 6 not built, 3 built. The migration keeps /venues/[id] as L2-04's route and moves the directory screen's navigation there; /directory-venues/[id] becomes a redirect.
  3. Three routes carry two pages each — /(tabs)/(sessions) (L1-03, L1-03c), /clubs/[clubId]/dues (L3-11, L4-14), /messages (L3-14, L3-15) — a pane or a sheet drawn as its own page. The synthesis claimed 0 shared routes and a build that fails on one; corrected (§ 5 below).
  4. L4-10 멤버 초대 claims the DM route. page-levels gives it /messages/add-members (the DM participants' add screen, opened only from dm-thread/participants-modal.tsx:158, which no board draws), but page-links opens it from four club pages (L2-01 130, L3-11 182, L3-25 218, L3-03 317: the club's 멤버 초대). Either L4-10 is two pages (the DM add-members form and a club invite) or the four club links want a page not yet drawn. Owner's call (§ 6).
  5. Member and person rows are mostly inert. 15 of the 29 links into L2-06 공개 프로필 are not built; eight rest on an explicit onPress={null} (club member rows, the 순위 stats modal, attendance and applicant rows, friend-request rows, add-friend results, the blocked list).
  6. Not-found doors are 돌아가기. NotFoundState has one action, appRouter.back(); the five drawn not-found doors (links 133, 153, 241, 249, 253) and L0-06's (18) are not built.
  7. The inventory was stale. page-inventory.json and the code map still listed /profile/practice after 51c6060c4 removed the route file: 84 route files today, 19 unclaimed, not 85 and 20. Regenerated and gated (§ 5).

4. What the three audits change in the build order ​

The synthesis (§ 5) ordered the build kit → pages → retirements → owed pages. The audits sharpen each step:

  1. Kit first, in the dependency order of § 1, not by board: text roles and Button before Cell, Cell before everything that hangs off it, Sheet before its four children. The five contradictions in § 1 are ruled before their piece is built, not after.
  2. Schema before pages, in three batches (§ 2): a page is built only once its batch has landed, so that no screen ships against a stub. The notes-and-trust batch comes first (it unblocks L1-05, L2-06, L3-01, L3-04, L3-08, L3-25, L3-38); money second (L2-01's 회비 pane, L3-11, L3-46, L4-14); sessions third (L2-02, L3-36, L3-38, L3-39, L3-47).
  3. Navigation fixes ride with their pages, with three exceptions done first because many pages depend on them: the /venues/[id] ↔ /directory-venues/[id] swap (§ 3.2), the gear sheet leaving its route (§ 3.1), and a NotFoundState that takes the drawn door (§ 3.6). Inert person rows (§ 3.5) are closed page by page; each page's link rows in the navigation table are its checklist.
  4. First pages. The pages whose data contract is covered and whose kit pieces fall in waves 1–4 of § 1 come first: the forms and settings pages (L4-01 … L4-06, L4-11, L4-20, L4-21), the lists (L3-13 알림, L3-14/15 메시지, L3-19 … L3-24), then L1-03 경기 and L1-04 기록 (L2-02 일정 once the sessions batch lands). The hubs (L1-01, L1-05, L2-01) wait for Block and the trust read.

5. Corrected and gated now ​

  • scripts/generate-page-inventory.mjs --check compares the inventory's route set with the route files (only the routes: the file's timestamp, commit and git change counts would false-drift); yarn docs:check-page-inventory is in yarn docs:check. scripts/lib/page-inventory-drift.mjs holds the comparison, scripts/__tests__/page-inventory-drift.test.mjs its three cases.
  • scripts/generate-page-code-map.mjs counts and lists the routes two pages share ("Routes two pages share"); the synthesis § 3 sentence that claimed none is corrected.
  • The inventory and the code map are regenerated (84 routes, 19 unclaimed).
  • Not gated, by design: kit parity and navigation parity are judgement over code (a prop's meaning, a push inside a shared file). The tables in audits/ are the baseline; a rerun diffs against them.

6. Decisions — ruled by research ​

The owner (2026-11-01): "As for all open decisions, I generally will say do research, find research-based answers and implement." The five questions of the audits and the four older ones, ruled; each lands with the piece or batch it belongs to.

DecisionRulingEvidenceLands with
L4-10 멤버 초대 claims the DM route; four club pages open itTwo things. L4-10 stays the DM's add-people form (its board says so: /messages/add-members (?threadId) from the thread's participants Sheet). The club's 멤버 초대 is a Sheet on the club pages (share the link and the code, as the app's invite sheet does today), not a form page; the four links (130, 182, 218, 317) are redrawn as that Sheet and leave page-links.Slack separates "invite people to the workspace" (outsiders, by link or email) from "add people to a channel" (members already in): Slack help. Inviting into a club is the first; adding to a thread is the second.the next board batch (the four pages' Sheet), then L2-01/L3-03/L3-11/L3-25
ExpandToggle: the kit's count Button or the chevron-only rowThe kit's full-width tonal Button stating the count ("12개 더 보기"); the chevron-only row goes when the Cell wave lands.A show-more control should say what more is and how much: Dynatrace design system, NYC design system, Wear OS Material's "Show more" (label + chevron, Android); a chevron alone has no accessible name. Review 48 ruled the same; the 09-28 "no text-based control buttons" ruling was about commands, not disclosure.kit wave 2 (Cell, CellGroup)
Not-found pages: the drawn doors or 돌아가기The drawn doors: 홈으로 가기 on every not-found state, plus the context door the board draws (매치 찾기 on a closed share link). NotFoundState gains a primary door and keeps 돌아가기 as the secondary.An error page must be constructive and offer the way on, with a link home: NN/g, Improving the dreaded 404.the first page that mounts NotFoundState (L2-02)
player_monthly_stats.practice_sessions (always 0)Drop it with the progression system's own rework, not alone: the column feeds only the radar's contribution petal (progression.rules.ts:224), and no board draws the radar (grep radar/petal/꽃잎 over flat-v2-*.jsx: 0), so the whole system is retired or redrawn by the migration and the column goes in that migration.the code: progression.rules.ts, progression.mapper.ts; the boardsthe records schema batch
The other seven auditsAs proposed: states, copy and accessibility per page as it is built (3, 4, 8); roles, deep links, signals and performance as release gates (6, 7, 9, 10).synthesis § 6; each needs a built page to auditper page / release
한 경기 시간 may stay 미정 (L4-01)Stays optional. The session's length is the court slot; the per-match length (matchDurationMinutes) feeds only the capacity estimate (rounds = session length ÷ match length), which is omitted while it is unset; the generators take format, courts and players, never a length.the code: session-wizard.store.ts:90, domain/rules/session-calculator.ts, the strategies' inputs—
세트 수 is a picker, not a count (L4-06)Stays a picker: it chooses a named scoring format (best of 3, pro set, Fast4: best of two short sets to 4 with a tie-break at 3–3), not a number.ITF Rules of Tennis, the appendix of approved alternative scoring methods (2026 rules); LTA Fast4 rules—
The club in the person Block is a fact, not a door (L2-06)Stays a fact. One door per subject on a Block (identity canon); the club is reached from its own list. UTR reaches a club from Clubs and Teams, not from the player card.library § Block identity; UTR help, Post a Score – Clubs—
Two head titles (9월 장부 over 장부's tiles)Stays.review 52—

7. Limits ​

Everything was read, not run: role gates, entitlements and first-run states count as built when their code is present; a push in a file shared by two screens was attributed by reading the file (each row names file:line for a recheck); [control differs] is a judgement against the drawn via. Pitches and sizes of legacy components were not measured again (the library's measurements stand). The data contract joins the boards' spec rows to the data layer by name; a page whose board is silent on a read is marked unsure, not covered.

Markdown remains the source of truth. Run yarn docs:check before handoff.