Durable file-by-file tracker for porting Artistant from iOS (~/Desktop/ios-swift)
to Android. FEATURE_CHECKLIST.md stays task-oriented; this matrix is
iOS path → Android target → status.
Status: done · partial · missing · obsolete
Product truth (Jul 2026, iOS): chat redaction gone (mig 0071); booking =
request → accept (pending_confirm, “Request sent.”, artist Accept/Decline);
Airbnb-style trust (safety banner + “always communicate through Artistant” +
report). Schema through ~0085. Android must not rebuild the retired redaction
moat.
Android current (Sep 2026): M0–M7 on main, then the "Artistant iOS
Light" redesign — P1 foundation (#141) and ten of the eleven P2 sections on
main: DS (#142), AP (#143), GS (#144, #149), MS (#145), BC (#146), BN (#147),
WZ (#148), AS (#150), PK (#151), SH (#152). AC (Account & settings) is still in
flight, so the Profile / Settings rows below are the ones to distrust.
docs/REDESIGN_2026-09.md is the plan and the token sheet. Every row that has
been through the redesign carries its section tag and its design numbers — a row
with neither has not, and saying which is what this file is for.
Re-implemented against the Sep-2026 light design (section GS, screens 01 · 11 ·
12 · 13 · 27 · 28 · 29 · 30 · 31 · 62 · 71 · 90 · 114 · 118 · 119) —
docs/REDESIGN_2026-09.md §P2. The "design" column is the extracted screen the
Android file now mirrors.
| iOS path | Design | Android target | Status | Notes |
|---|---|---|---|---|
| — (launch window) | 01 Onboarding | feature/signup/SplashScreen.kt |
done | "The one dark room" — the only dark surface in the app, rendered on RootGate.Loading in darkest so the pre-Compose launch window hands off with no seam. Carries no actions: the markup's two CTAs belong to 118 |
Screens/Signup/SignupWelcomeView.swift |
118 Welcome — blocked | feature/signup/WelcomeScreen.kt |
done | Disabled CTA always paired with an inline reason ("Tick this to continue"). Only real conditions gate it — the terms tick, plus a caller-supplied connectivity reason. No "signups paused" state: app_settings is server-only, so it cannot be stated truthfully |
Screens/Signup/CommunityCommitmentView |
27 Community pledge | feature/signup/CommunityCommitmentScreen.kt |
done | Four numbered rules, a required tick, "Shown once — you won't see this again". The Decline dead-end screen is gone; back is the decline |
Screens/Signup/SignupRoleView.swift |
11 Role picker · 71 Hydration error | feature/signup/RoleScreen.kt |
done | Select on tap, move on Continue (was: commit-and-self-advance). 71 is the same screen with a failure banner + Retry, which also renders on the handle step — the one the gate actually enters on |
Screens/Signup/SignupAuthView.swift |
12 Sign in | feature/signup/SignupAuthScreen.kt |
done | Phone OTP first, then Apple / Google / password. Only three number shapes are accepted (10 digits, 91+10, +91+10) and anything else gets the inline reason — a longer paste is never trimmed to its last ten digits. LOGIN sends with createUser = false, so that door cannot mint an account behind an un-ticked consent box; GoTrue's refusal becomes "No account for this number — create one?" back to the welcome screen. Apple and Google marks not bundled yet, so those rows are labelled buttons |
| — (new) | 119 Enter code | feature/signup/EnterCodeScreen.kt |
done | SignupStep.Code, 6-box OtpField, 30s resend, "Change number", email escape after two sends. Every way back out calls AuthViewModel.clearOtp() — including the system gesture, which SignupFlow handles, since the activity-scoped VM otherwise carried the spent send count into the next number |
Screens/Signup/EmailAuthView.swift |
28 Email sign-up | feature/signup/EmailSignUpScreen.kt |
done | A modal over the auth step (emailSignUp flag), not a step. One button, two acts: the password is offered to sign-IN first and only creates an account when nothing matched it, which is what the banner above it promises. Sign-in opens at GoTrue's 6 characters (an older account may hold one); the 8 the tick draws is enforced on the branch that creates. "Forgot password?" calls resetPasswordForEmail |
Screens/Signup/SignupProfileView.swift |
29 Handle & city · 90 Handle taken | feature/signup/ProfileScreen.kt |
done | Four live states; "Couldn't check" never borrows the available tick, while still leaving Continue tappable (the unique constraint is the backstop). Taken offers HandleSuggestions. 90's "THE OTHER THREE STATES" panel is design documentation, not a control, and is deliberately not drawn |
Screens/Signup/SignupNotifPermissionView.swift |
13 Notifications | feature/signup/NotifPermissionScreen.kt |
done | Names the loss ("Quotes expire. We'll tell you first."), three kinds. FCM register via PushService (soft without google-services.json) |
Screens/Signup/SignupDoneView.swift |
30 You're in | feature/signup/DoneScreen.kt |
partial | Ends on the score. The design's "412 acts play your city" has no count endpoint behind it, so the sentence keeps its shape and drops its number |
Screens/Signup/LegalView.swift |
31 Terms · 114 Privacy policy | feature/signup/LegalScreen.kt |
done | One segmented viewer, both documents one tap apart. Copy rewritten to the redesign's — the old 11-section terms described platform fees and refunds on a product that takes no payment |
| — (new) | 62 Privacy | feature/signup/PrivacyScreen.kt |
partial | ONE switch (read receipts), stored in PrivacyPreferences (DataStore) because it has no column in the 107 canonical migrations, and the screen says so. Nothing reads it yet — enforcement is feature/messages, under the key privacy.read_receipts. The design's second switch, "Show my city", is a stated fact instead: users.city has no visibility column, so a device flag could only have hidden the city from its owner. Reached from the account settings list's Privacy row |
Screens/Signup/SignupFlowView.swift |
— | feature/signup/SignupFlow.kt + SignupViewModel |
done | Step machine gains .Code, retired by a live session exactly like .Auth. The floating hydration strip is gone — the banner lives in the screens now |
Services/AuthService.swift / Root gate |
— | SessionManager + ArtistantNavHost / RootViewModel |
done | sendPhoneOtp / verifyPhoneOtp / sendEmailOtp / verifyEmailOtp / sendPasswordReset added, the two sends taking createUser from the entrance; signUpWithEmail also reports AlreadyRegistered (GoTrue's empty-identities answer). AuthGateway is the interface the auth VM injects, so its branches are unit-testable; every prior method is otherwise unchanged |
Services/AppleSignInController.swift |
— | SessionManager Custom Tabs + deep link |
done | #12 deepLinkError surface on OAuth denial |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
Screens/DiscoverView.swift |
feature/discover/DiscoverScreen.kt |
redesigned (DS) | Sep-2026 light design, screens 02 + 59. Header (city + today) · SearchBarButton · category chips · one 262dp HeroCard · titled two-up Tile rails · SkeletonPage. Hero carousel and full-bleed insets retired; "See all" seeds Search via SearchSeed. The header circle is the design's bell → activity, its dot driven by ActivityLog's unread count; Saved lost this borrowed slot and keeps its Profile row (26) |
State/DiscoverFeedStore.swift |
feature/discover/DiscoverViewModel.kt |
redesigned (DS) | Hero + 4 rails via 5 concurrent search_artists; the availability rail is date-scoped through the 0073 p_date AND re-filtered on days_available (the repo silently retries without the 0073 dims on an old server). Empty rails are dropped, not drawn |
Screens/SearchView.swift |
feature/search/SearchScreen.kt |
redesigned (DS) | Screens 14 / 03 / 57 / 58 in one destination. Browse-vs-results is hasActiveQuery × a local editing flag. Suggestions carry search_facets' real counts; "Notify me when one joins" omitted (no alert store); "Map view" omitted (no coordinates) |
Screens/SearchFilterSheet.swift |
feature/search/SearchFilterSheet.kt + CompareByServiceSheet.kt |
redesigned (DS) | Screens 15 / 104 (one sheet, two states: chip summary + disclosure rows + histogram + pinned CTA) and 53 (radio compare-by-service). "Must have" toggles omitted — no PA / verification / travel columns |
State/SearchStore.swift |
feature/search/SearchViewModel.kt |
redesigned (DS) | Debounce + pagination + filter/histogram, plus selectService (radio), dropFilter(kind) and the SearchSeed collector. Every computed string is a pure function in SearchLabels.kt, unit-tested |
Screens/ArtistView.swift |
feature/artist/ArtistProfileScreen.kt |
done | Redesigned Sep 2026 (AP, design 04/54/55/101/103). Round portrait + rating pill + three-cell stat strip; packages replace the rate card and the price rides the CTA; skeleton with no nav bar (54); named not-found with a route to Discover (55); no-audio redirect (101); self view swaps the verbs and drops the booking controls (103). Hero pager (PROF-10) still open |
Screens/ScoreExplainerView.swift |
feature/score/ScoreExplainerScreen.kt |
done | Redesigned Sep 2026 (design 50/79/80). Score · Stats · Opportunities segments; New is a gig counter and says "not a low score, no score"; a failed read leads with "This isn't your real score"; every opportunity opens the press kit or the wizard |
Screens/ScoreBreakdownSheet.swift |
feature/score/ScoreBreakdownSheet.kt |
done | Redesigned Sep 2026 (design 99). Degraded state keeps the server's number and itemises only what the artist row can back (total_gigs, on_time_rate, the loaded reviews); the rest go under NOT LOADED with no bar to misread as a zero |
| — (new, design 16) | feature/score/BookabilityScreen.kt |
done | Client-facing audit of one artist's score, pushed from the breakdown sheet. Route bookability/{artistId} |
| — (new, design 102) | feature/artist/ArtistReviewsScreen.kt |
done | Every review for one artist + search + All/5 star/Recent lenses. Three distinct empties: no corpus, nothing in this lens, no match for a query (which quotes the corpus size). Route artist_reviews/{artistId} |
iOS ReportArtistSheet |
feature/artist/ArtistProfileSheets.kt (ReportArtistSheet) |
done | Design 56. Real reports insert via ReportsRepository.reportArtist; five profile-specific reasons + optional note; the toast says Sent or Queued and never "received" |
Screens/ArtistListView.swift |
feature/profile/ArtistListScreen.kt |
redesigned (DS) | Screens 32 + 112. Kind chips are permanent navigation (switching replaces the destination); a within-list category rail derived from the rows; rows carry the accent score chip and "from ₹x". Failure ≠ empty |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
Screens/BookingView.swift |
feature/booking/BookingScreen.kt |
redesigned (BC) | Screen 05 "Step 1 of 2". Month grid replaces the 14-day strip; opens on the first month with an open day, stops at the current month going back; closed days dim AND refused by the ViewModel. Time/venue/guests stay on this step — 06 is read-only and start_datetime needs the time |
Screens/CheckoutView.swift |
feature/booking/CheckoutScreen.kt |
redesigned (BC) | Screen 06 "no money in v1". One number (artist fee), accent note on the no-payment terms, "what happens next" card. Date/time moved into the act header; the per-row edit chevrons are gone with them |
Screens/ConfirmedView.swift |
feature/booking/ConfirmedScreen.kt |
redesigned (BC) | Screen 07. Two branches: pending reads "Request sent.", confirmed reads "You've got a band on Saturday." with Message + Add to calendar. "Confirmed in 4 minutes" omitted — the row records no answer time |
| — (new) | feature/booking/MatchConfirmedScreen.kt |
redesigned (BC) | Screen 94, route match_confirmed/{bookingId}. The chat path's own landing; messaging navigates here from an accepted in-thread quote |
| — (new) | feature/booking/InvoiceScreen.kt + InvoiceLogic.kt |
redesigned (BC) | Screen 132, route invoice/{bookingId}. "A record, not a tax invoice": fee, Artistant's ₹0, total = fee; persisted platform fee and GST are never printed as charges. Shares text, not a PDF |
Screens/GigRequestDetailView.swift (counter sheet) |
feature/booking/CounterOfferScreen.kt |
redesigned (BC) | Screen 61, route counter_offer/{requestId} on the ARTIST graph — gig_requests_update_artist (mig 0002) is the only UPDATE policy, so a client-side counter is blocked on the backend. Since AS landed it is the ONLY way to counter: the gig detail's inline dialog is gone |
Screens/RequestQuoteView.swift |
feature/booking/RequestQuoteScreen.kt |
redesigned (BC) | Screen 17 "the brief, prefilled". Occasion chips + date/start pickers + guests/venue/budget + 500-char note; occasion and start time have no column and travel in the message (quoteBriefMessage). Budget is one amount, not the design's range |
Screens/BookingDetailView.swift |
feature/booking/BookingDetailScreen.kt |
redesigned (BN) | Five variants behind one route — confirmed (18), awaiting (95), cancelled (83), disputed (96), read-only (97) — plus not-found (84) and the two-stage cancel flow (117 → 52). Run of show is built only from start_datetime/end_datetime/venue_notes; the design's invented load-in and the disputed screen's event history have no schema behind them and are stated as unavailable |
Screens/BookingsView.swift |
feature/bookings/BookingsScreen.kt |
redesigned (BN) | Screens 10 / 89 / 122. Upcoming ⁄ Past, three affordances (confirmed card · awaiting row · review row), dismissible name nudge over the empty state, and an offline list rendered from a DataStore snapshot of the night's essentials |
Screens/ReviewSheet.swift |
feature/booking/ReviewSheet.kt |
redesigned (BN) | Screens 20 / 98. Stars + a word, four tags onto the real reviews.categories keys, optional prose. A missing artist name degrades to the booking reference and never blocks the review. No "post publicly" switch — reviews has no visibility column |
| — (design 78) | feature/bookings/MonthCalendarScreen.kt |
new (BN) | The shared calendar as its own destination (month_calendar), off the Bookings header. Renders MonthCalendarCard + the selected day's schedule |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
Screens/MessagesView.swift |
feature/messages/MessagesScreen.kt |
done | Redesign 19/110. Server inbox + All/Bookings/Inquiries/Support chips + push deep link; rows resync on resume (iOS uses an all-threads Realtime sub for the same job). Rows carry DEAL STATE — a live gig_requests quote replaces the preview with amount + expiry. Permanent Artistant Support row in every state |
Screens/ChatView.swift |
feature/messages/ChatScreen.kt |
done | Redesign 08/70/88. Realtime + optimistic send/retry; three message states (sent / read / failed-with-retry); trust banner; quote-as-object with Accept/Counter → match_confirmed/{bookingId}; accept narrated in three phases |
Screens/ThreadDetailsSheet.swift |
feature/messages/ThreadDetailsSheet.kt |
done | Redesign 33. Booking card (status/date/venue/fee/score) or the inquiry banner; archive/mute/mark-unread/star/block rows; report opens the picker |
| — (iOS files reports from the thread sheet) | feature/messages/ReportConversationSheet.kt |
done | Redesign 73. Reason → optional note → Submit, same shape as report-artist. ConversationReportReasons; the note reaches reports.details |
| — (no iOS equivalent) | feature/messages/ArchivedScreen.kt |
done | Redesign 60/111. Archive is a route, not a sheet. Device-local flag (threads has no archived column); the badge rule is printed on the screen and pinned by a test |
| — (no iOS equivalent) | feature/messages/SafetyCentreScreen.kt |
done | Redesign 131. Three rules + one-tap remedies. The design's "Emergency help · local numbers by city" row is NOT drawn — no per-city data exists |
Screens/SupportView.swift (n/a) |
feature/messages/SupportChat.kt |
done | Redesign 34. Scripted on-device branch tree that says it is one; full screen, option cards, hand-off to the bookings tab; typed notes → app_feedback |
| — (0087 has no iOS screen) | feature/profile/BlockedAccountsScreen.kt |
done | Redesign 127. Copy states what a v1 block actually does (client-side filtering); the design's stronger claim is deliberately not shipped. "Block ≠ report" carries a live link to the inbox |
Screens — Artist studio (section AS, design 09 / 85 / 86 / 133 / 36 / 35 / 107 / 108 / 109 / 22 / 105 / 106)
| iOS path | Android target | Status | Notes |
|---|---|---|---|
Screens/ArtistHomeView.swift |
feature/artisthome/ArtistHomeScreen.kt + ArtistStudioLogic.kt |
redesigned (AS) | Screens 09 / 85 / 86. Money first, score second, requests third — the accent card leads with what the month has PLAYED plus what is agreed and still ahead; "awaiting settlement" is not said, because nothing is in custody. dashboardMode picks between three screens: a failed FIRST bookings read is Unavailable (em-dashes, inert strip, an explicit refusal), never Cold — an unshaded 14-day strip reads as fourteen open nights. Nothing on the page mutates: the design's inline Accept would commit the irreversible answer with the clash warning three taps away |
| — (new, design 133) | feature/artisthome/EarningsScreen.kt |
new (AS) | "Earnings without custody" — twelve trailing months of AGREED fees, route earnings. Row states are Agreed / Played, not the design's Settled: settlement is not observable here. Buckets by gig date, excludes pending_confirm so declining a request cannot make earnings fall. Says outright it is not a payout statement |
Screens/ArtistGigsView.swift |
feature/gigs/ArtistGigsScreen.kt |
redesigned (AS) | Screen 36, "calendar, then the day". The rewrite is a deletion — the month-grouped list below the grid is gone, so a booking no longer appears twice; the grid is the index and the list is one day. The selection is never null (initialDayFor). Day rows draw the clock stacked through the shared ClockColumn |
Screens/GigRequestDetailView.swift |
feature/gigs/GigRequestDetailScreen.kt + GigRequestLogic.kt |
redesigned (AS) | Screens 35 / 107 / 108 / 109 behind one route. Clash warning sits ABOVE the dock — only one of the three answers is irreversible and there is no un-accept. Counter leaves for counter_offer/{id} (screen 61, BC). Withdraw (107) has no write path and Message (107/108) has no thread — one is created on ACCEPT (mig 0047) — so neither is drawn |
| — (design 21 Open gigs) | — | blocked | gig_requests_select_participants (mig 0002, narrowed by 0009) is auth.uid() = artist_id or auth.uid() = client_id, so an artist cannot read a request addressed to somebody else. A feed of other hosts' requests has no data source, and the match percentage has no column |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
Screens/ArtistWizard/ArtistWizardView.swift |
feature/wizard/WizardScreen.kt |
done | Redesigned Sep 2026 (design 72). Back circle + progress track + "03 / 10" counter; Save & exit is a sheet stating what survives, reachable from every step. Publish: upsert + packages/tech RPCs + service tags + published; gallery/SAF + UploadQueue |
Screens/ArtistWizard/ArtistIdentityStep.swift |
feature/wizard/WizardFormSteps.kt (Identity) |
done | Redesigned Sep 2026 (design 37). AppTextField + Chip; public address under the handle; the category's seeded pricing band stated on the step that picks it. Genre stays a free-text field — the design draws chips, the column has no vocabulary |
Screens/ArtistWizard/ArtistLocationStep.swift |
feature/wizard/WizardFormSteps.kt (Location) |
done | Redesigned Sep 2026 (design 38). Base city + travel radius + event types. Radius and event types are draft-only (no radius column; no event_types writer in this client) and the step says so |
Screens/ArtistWizard/ArtistPricingStep.swift |
feature/wizard/WizardFormSteps.kt (Pricing) |
done | Redesigned Sep 2026 (design 24). Fee in, all-in out — the host's total per tier through BookingMath. No market-rate line: there is no such aggregate. replace_packages on publish |
Screens/ArtistWizard/ArtistTechStep.swift |
feature/wizard/WizardFormSteps.kt (Tech) |
done | Redesigned Sep 2026 (design 39). Presets as checkable rows, custom lines as chips. No rider PDF — tech_rider stores text rows. replace_tech_rider on publish |
Screens/ArtistWizard/ArtistAvailabilityStep.swift |
feature/wizard/WizardFormSteps.kt (Availability) |
done | Redesigned Sep 2026 (design 40). Day strip + start-time chips compressed to one badge, shown while still editable. No "notice you need" — no column for it |
Screens/ArtistWizard/ArtistCoverStep.swift |
feature/wizard/WizardMediaSteps.kt (Cover) |
done | Redesigned Sep 2026 (design 41). Camera / Library pair; a denied permission routes to the system settings page. Photo only — no video cover path |
Screens/EPKView.swift |
feature/epk/EpkScreen.kt |
done | Redesigned Sep 2026 (design 23/87/76) — a hub: completion meter + payoff line, cover, gallery strip, six section rows stating the fact or the gap's effect. Empty kit renders the invitation rows (87). Sections open sheets (EpkSheets.kt) or in-screen panes (EpkPanes.kt) |
Screens/Settings/ScoreHistorySheet.swift |
feature/score/ScoreHistoryScreen.kt |
done | Redesigned Sep 2026 (design 51) — a pushed screen, not a sheet. Per-recomputation deltas: score_history stores no reason column, so no per-event cause is invented. Route score_history |
Screens/PaywallView.swift |
feature/paywall/PaywallScreen.kt |
done | Play Billing wired; inert until subscriptionsEnabled |
Components/ScoreRing.swift |
designsystem/component/ScoreRing.kt |
obsolete | Ported with New-tier nil handling, but it has had no call site since the Sep-2026 redesign — feature/score draws its own ScoreDonut. Dead code, and the only raw literals in that section (64/6dp, 1.2sp); deletion tracked in #181 |
Components/Sparkline |
designsystem/component/Sparkline.kt |
obsolete | No call site. The studio's trend is a hand-rolled 4.5dp meter, not this. Dead code (raw 48/3dp); deletion tracked in #181 |
Screens/ArtistWizard/ArtistSocialsStep.swift |
feature/wizard/WizardMediaSteps.kt (Socials) |
done | Redesigned Sep 2026 (design 42). Paste fields, not OAuth — no link endpoint on this backend — and the banner says the links are not verified |
Screens/ArtistWizard/ArtistBioStep.swift |
feature/wizard/WizardMediaSteps.kt (Bio) |
done | Redesigned Sep 2026 (design 43). Live counter plus what-good-looks-like guidance at four lengths; service tags published via updateServiceTags |
Screens/ArtistWizard/ArtistSamplesStep.swift |
feature/wizard/WizardMediaSteps.kt (Samples) |
done | Redesigned Sep 2026 (design 44). Upload state read off UploadQueue — queued / uploading / failed with a retry — rather than a bar driven locally. SAF + UploadQueue after go-live |
Screens/ArtistWizard/ArtistPreviewStep.swift |
feature/wizard/WizardPublishSteps.kt (Preview) |
done | Redesigned Sep 2026 (design 45). Centred back header; seven rows, each stating its value with an Edit that jumps back to the owning step |
Screens/ArtistWizard/ArtistWizardDoneStep.swift |
feature/wizard/WizardPublishSteps.kt (Done) |
done | Redesigned Sep 2026 (design 46). Ends on the public address (copyable) and sets the New-tier expectation off ScoreBands |
| — (design 65) | feature/epk/EpkSheets.kt (AddCoverSheet) |
done | Take a photo / Choose from library; the video row is disabled and says so — ArtistMediaRepository uploads a normalised JPEG only, ArtistMediaKind.video has no writer, Transformer trim is not wired |
| — (design 67) | feature/epk/EpkSheets.kt (EditBioSheet) |
done | Bio + "What you offer" chips in one sheet; counter quiet until the 200-char cap, guidance beside it. Save flushes the shared debounce rather than opening a second write path |
| — (design 68) | feature/epk/EpkSheets.kt (AddPersonalitySheet) |
done | Answered cards sort above unanswered so the filled shape is what the empty prompts are read against; 280-char cap (ArtistPrompts.MAX_ANSWER_LENGTH) |
| — (design 74) | feature/epk/EpkSheets.kt (EditLinkSheet) |
done | One component, two titles; Remove only in edit mode; both fields validate through linkUrlProblem / linkLabelProblem |
| — (design 75) | feature/epk/EpkSheets.kt (AddAudioSheet) |
done | Picker MIME array IS the bucket's allowed_mime_types (mig 0010). One option, not iOS's two — Android's document picker already spans every provider |
| — (design 66) | feature/epk/EpkSheets.kt (StalledUploadsSheet) |
done | Per-item Retry/Discard then bulk; UploadQueue.retryFailed(id) / discardFailed(id). States that the queue is on disk. "stopped at 64%" becomes size + attempts — there is no byte counter to quote |
| — (design 135 Get verified) | — | blocked | No identity-verification path exists server-side. kyc_records is PAYOUT KYC (pan, cancelled cheque, bank; mig 0001) with every column not null, the kyc-documents bucket is PAN cards and cheques (mig 0010), artists has no verified column, and mig 0065 records that the KYC wizard step was removed from the shipping build. Shipping an Aadhaar upload would collect a government ID with nowhere to put it |
Screens/EPKView.swift |
feature/epk/EpkPanes.kt |
done | The section editors behind the hub's rows — gallery/reorder + palette, samples, packages, tech rider, links + connected accounts + share link. Moved, not rewritten: each keeps its whole-set-replace wipe guard and hydration gate |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
Screens/ProfileView.swift |
feature/profile/ProfileScreen.kt |
done | Redesigned Sep 2026 (AC, design 26). Client tab root only: identity, "Switch to artist mode" (one users.role write, then the root gate re-routes), the three-up band, and rows. Settings moved to AccountScreen. "Payment and billing" / "Invoices and GST" are not drawn — v1 takes no money and neither row has anything to open. The old Help/Feedback sheet is gone: section SH replaced it with real help_centre / feedback screens, and the account list's "Help centre" row pushes SH's — FeedbackScreen files into the same app_feedback behind an outbox the sheet had no notion of. "Saved artists" is now the ONLY route to artist_list/saved, Discover's header having taken the bell the design draws |
Screens/ProfileView.swift (settings list) |
feature/profile/AccountScreen.kt |
done | New Sep 2026 (AC, design 47/69). ONE list, two hosts — the ARTIST group is injected by role, never forked. Stat band swaps columns per role. Route account on both graphs; the artist's old profile route is retired. Carries the entry point for every System screen the design gives none: Activity (123, above Notifications — the log, then the setting), and a support tail of Help centre, Send feedback (64), What's new (137, a request to the root's WhatsNewViewModel) and Rate Artistant (138, AppStore.openListing) |
Screens/Settings/DataExportView.swift |
feature/profile/DataExportScreen.kt |
done | Redesigned Sep 2026 (AC, design 49/81/82/113). A sealed ExportState makes "failed with a file" unrepresentable; the expiry is the server's, never invented; a failure ships nothing |
| — (iOS delete is a dialog) | feature/profile/DeleteAccountScreen.kt |
done | New Sep 2026 (AC, design 48/115/116). Reason → consequences → receipt. Stage 2 names every loss and only counts what it read; stage 3 itemises the 30-day backup window and leaves it unticked |
| — | feature/profile/NotificationSettingsScreen.kt |
done | New Sep 2026 (AC, design 124). Eight DataStore toggles, marketing off by default, quiet hours named. POST_NOTIFICATIONS is a real read with a route to the system screen. No server-side preference exists — the screen says so |
| — | feature/profile/AccessibilityScreen.kt |
done | New Sep 2026 (AC, design 129). Two real app-side controls (tab-bar labels, autoplay); text size / reduce motion / bold / contrast are system-owned on Android and are stated as facts with links, not faked as switches |
| — | feature/profile/LanguageScreen.kt |
partial | New Sep 2026 (AC, design 130). Per-app locale via the platform LocaleManager (API 33+; no appcompat). Only English has resources, so the other five render with the reason and are not selectable. Region is three facts — India/INR only in v1 |
| — | feature/profile/DevicesScreen.kt |
partial | New Sep 2026 (AC, design 128). This device from Build, plus "Sign out everywhere else" (SignOutScope.OTHERS). Supabase exposes no session list and there is no activity table, so neither is drawn — blocked on backend |
| — (no iOS screen) | Tax details (design 134) | blocked | No legal_name/trading_as/pan/gstin/billing-address columns on users or artists. kyc_records.pan is payout KYC (v1 has no payouts) and its siblings are not null |
| — (no iOS screen) | Invite a friend (design 125) | blocked | No referral/invite ledger anywhere in the 105 canonical migrations. Sent/joined/earned cannot be read, so the screen is not shipped |
Screens/Settings/ManageAvailabilityView.swift |
feature/availability/ManageAvailabilityScreen.kt |
redesigned (AS) | Screens 22 / 105 / 106, "the calendar is inventory". The month leads, drawn with the SHARED MonthDayGrid (it gained one optional unavailableDays parameter rather than a second grid that disagreed about a booked day) and no selectable day. The preview shows the MISSING badge on an empty week (105). Two refusals: a failed bookings read marks nothing as taken and says so, and a failed seed disables Save (106) so an empty working copy cannot wipe real data |
Screens/Settings/ScoreHistorySheet.swift |
feature/score/ScoreHistoryScreen.kt |
done | Redesigned Sep 2026 (design 51) — a pushed screen, not a sheet. Per-recomputation deltas: score_history stores no reason column, so no per-event cause is invented. Route score_history |
Screens/PaywallView.swift |
feature/paywall/PaywallScreen.kt |
done | Redesigned Sep 2026 (AC, design 25/91/92/93). Four states derived by proStateFor from EntitlementStore + the store query, so an outage can never read as a lost plan. One plan card (Play carries one product); the active screen drops the design's fee arithmetic, which describes a product that moves money |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
| Root / role tabs | ArtistantRoot + ClientTabsScaffold / ArtistTabsScaffold |
done | All primary tabs wired (no Placeholder) |
State/TabRouter.swift |
nav + deep-link pending channels | done | TabRouter singleton + client/artist scaffold consumers |
ClientRoute.ArtistProfile / Search |
Routes.kt + NavHost |
done | Wired in ClientTabsScaffold |
App-level surfaces with no feature of their own. Every route below is registered
on BOTH graphs (SystemRoutes); the two gates and the two sheets are presented,
never pushed.
| Design | Android target | Status | Notes |
|---|---|---|---|
| 77 Toast | feature/system/ToastController.kt + ToastViewModel + the host in navigation/ArtistantNavHost.kt |
done | ONE host, above the NavHost and above every gate tier, so a confirmation raised by a screen being popped still renders. ToastHost gained a key param: keyed on the text alone, a repeat of the same string was dismissed early by the previous toast's running timer. Never hit-tests when clear (the node is not composed). Bottom padding clears the tab bar through the new lightTabBarHeight() |
| 123 Activity | feature/system/ActivityScreen.kt + ActivityLog.kt + ActivityViewModel.kt, route activity |
done | Local, per-account log of pushes RECEIVED on this device (DataStore), written from ArtistantMessagingService.onMessageReceived before the permission check. There is no notifications table in the shared schema and the screen's subtitle says so. Four chips as drawn (All / Bookings / Quotes / Reviews) — a chat push is reachable under All only, because the design draws no Messages chip. Rows route through PushPayloadRouter, the same function the notification tap uses. Reached from the Discover bell (02, dot = unread) and the account list's Activity row; design 09 puts a "Taking gigs" pill where the client has a bell, so the row is the artist's only way in |
| 120 Update required | feature/system/UpdateRequiredScreen.kt |
built, gate unwired | app_settings (mig 0016) is RLS default-deny and mig 0037 revoked the app_setting() grant, so no client can read a minimum version. LiveSystemStatusSource reports Normal forever; reachable on a debug build via the harness flag force-update. No back control and BackHandler swallows the gesture — the design's own footnote says there is no dismiss |
| 121 Service outage | feature/system/ServiceOutageScreen.kt |
built, gate unwired | Same source, harness flag service-outage. Keeps its back control (the design draws one) and a "Check again"; startedLabel is null because nothing on the device knows when an outage began, so the line is omitted rather than invented |
| 137 What's new | feature/system/WhatsNewSheet.kt + ReleaseNotes.kt + WhatsNewViewModel.kt, presented from the root |
done | Once per BuildConfig.VERSION_NAME (DataStore) from a compiled notes table — there is no releases table, and notes describe the binary so they ship with it. A null seen-version records silently: greeting a fresh install with "what's new" is the worse error. Over the tab shell only — WhatsNewHost() in ArtistantNavHost, composed whenever the gate is Tabs and nothing blocks. The account list's row calls showOnDemand() on that same root-scoped VM (a scaffold sits above any NavHost, so hiltViewModel() resolves the activity's), ignoring the seen-version record and falling back to the most recent authored note |
| 138 Rate Artistant | feature/system/RatePromptSheet.kt + RatePrompt.kt, presented from ClientTabsScaffold |
done | Armed ONLY by a completed booking review, asked once, never on launch. The trigger is resolved in the scaffold by watching the ReviewSheetViewModel the booking screen already takes as a parameter, so feature/booking is untouched. No Play in-app review library (no new Gradle deps): the button opens the store listing and the copy says so. The account list's "Rate Artistant" row opens the same listing directly — a way in for somebody who did not wait to be asked, without re-arming a prompt whose point is when it appears |
| 63 Help centre | feature/system/HelpCentreScreen.kt + HelpContent.kt + HelpCentreViewModel.kt, route help_centre |
done | Promotes the one blocking item the app can honestly detect (a blank users.full_name) above an audience-switched FAQ with ranked search. Answers expand in place — the design's chevron means a pushed article on iOS and there is no article store, server-side or compiled |
| 64 Feedback | feature/system/FeedbackScreen.kt + FeedbackViewModel.kt + FeedbackOutbox.kt, route feedback |
done | Writes app_feedback through the existing BookingsRepository.submitFeedback; shows the column's own 1–2000 constraint (mig 0073) as a live counter; the design's "it queues on this device and sends on your next live session" is backed by a real DataStore outbox drained by a CONNECTED-constrained FeedbackWorker. Reached from the account list's "Send feedback" row on both graphs |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
Repositories/UsersRepository.swift |
data/repository/UsersRepository.kt + Fake |
done | M1 |
Repositories/ArtistsRepository.swift |
data/repository/ArtistsRepository.kt + Fake |
done | M2 — id-keyed hydrating cache |
Repositories/SearchRepository.swift |
data/repository/SearchRepository.kt + Fake |
done | M2 |
Repositories/SearchTypes.swift |
data/model/SearchTypes.kt |
done | M2 |
Repositories/SavedArtistsRepository.swift |
data/repository/SavedArtistsRepository.kt + Fake |
done | saved_artists upsert/delete/list + SavedStore |
Repositories/ReviewsRepository.swift |
data/repository/ReviewsRepository.kt + Fake |
done | M2 profile (listForArtist) |
Repositories/PackagesRepository.swift |
data/repository/PackagesRepository.kt + Fake |
done | replace_packages |
Repositories/ArtistMediaRepository.swift |
data/repository/ArtistMediaRepository.kt |
done | Photo upload + reorder RPC |
Repositories/ScoreRepository.swift |
data/repository/ScoreRepository.kt + Fake |
done | metric_* + score_history |
Repositories/BookingsRepository.swift |
data/repository/BookingsRepository.kt + Fake |
done | M3 — request→accept + calendar ingest |
Repositories/RequestsRepository.swift |
data/repository/RequestsRepository.kt + Fake |
done | M3 |
Repositories/MessagesRepository.swift |
data/repository/MessagesRepository.kt + Fake |
done | Explicit projections, Realtime, receipts |
Repositories/SamplesRepository.swift |
data/repository/SamplesRepository.kt |
done | Append upload + targeted delete |
Repositories/TechRiderRepository.swift |
data/repository/TechRiderRepository.kt |
done | replace_tech_rider |
Repositories/ArtistLinksRepository.swift |
data/repository/ArtistLinksRepository.kt |
done | CRUD |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
State/OnboardingStore.swift / signup |
SignupViewModel |
done | M1 |
State/RoleStore.swift |
AppPreferences + RootViewModel |
partial | Role persisted; store shape differs |
State/DiscoverFeedStore.swift |
DiscoverViewModel |
done | M2 |
State/SearchStore.swift |
SearchViewModel |
partial | M2 |
State/SavedStore.swift |
feature/saved/SavedStore.kt |
done | Optimistic toggle + prefs + sign-out reset. refreshFromServer() returns whether the SERVER copy was read (DS) — loaded-and-empty and couldn't-load are the same empty set and the opposite meaning |
State/BookingStore.swift |
BookingViewModel + BookingDraftStore |
partial | Draft store + VM; no global booking list cache yet |
State/RequestStore.swift |
ArtistHomeViewModel + GigRequestDetailViewModel (+ RequestsRepository) |
done | Requests list on the studio dashboard and the four-state request screen; the derivations are pure in GigRequestLogic.kt (AS) |
State/MessageStore.swift |
MessagesViewModel / ChatViewModel |
done | Inbox filters + Chat Realtime/optimistic + scroll-back cursor (loadOlder); no global MessageStore needed |
State/EPKStore.swift |
EpkViewModel |
done | Packages/tech/links/samples writes |
State/ArtistOnboardingStore.swift |
WizardViewModel |
done | Publish orchestration + pending media |
State/EntitlementStore.swift |
feature/paywall/EntitlementStore.kt |
done | Checkout gate ready; inert until subscriptionsEnabled |
State/TabRouter.swift |
navigation/TabRouter.kt |
done | pendingThread/booking/gigRequest + role tabs |
State/Persistence.swift |
AppPreferences (DataStore) |
done | M0 |
State/WizardMediaCache.swift |
platform/media/WizardMediaCache.kt |
done | cacheDir staging |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
Services/SupabaseClient.swift |
SupabaseClientFactory + SupabaseModule |
done | M0; tier guard |
Services/AppEnvironment.swift |
core/config/AppEnvironment.kt |
partial | M0 + subscriptionsEnabled/legal URLs (flag default off) |
Services/AuthService.swift |
platform/auth/SessionManager.kt |
done | M1 |
Services/AccountService.swift |
data/repository/AccountRepository.kt + Fake |
done | M6 — delete-account + data-export EFs |
Services/PushService.swift |
platform/push/PushService.kt + MessagingService |
partial | claim_device_token FCM register + payload→TabRouter; needs operator google-services.json + send-push FCM |
Services/CalendarSyncService.swift |
platform/calendar/CalendarSyncService.kt |
done | CalendarContract mirror + planner |
Services/PermissionsService.swift |
NotificationPermission etc. |
partial | Notif permission UI M1; calendar runtime grant |
Services/UploadQueue.swift |
platform/media/UploadQueue.kt |
done | JSON snapshot + WorkManager drain + resumeAfterLaunch |
Services/VideoTrimmer.swift |
— | deferred | Was ported, deleted Aug 2026: no screen picks video in v1, so it had no caller (and no test). Re-add with the cover-video step, together with media3-transformer |
Services/Payments/* |
platform/billing/PlayBillingService.kt |
done | Wired; inert until subscriptionsEnabled |
Services/Observability/* |
Analytics / Crash |
partial | Dark-until-key stubs M0 |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
| Booking money math | domain/booking/BookingMath.kt |
done | M0 |
| Score bands | domain/score/ScoreBands.kt |
done | M0 |
| Chat redaction regexes | domain/chat/Redaction.kt |
obsolete | Deleted Jul 2026 wave — do not reintroduce. |
| Returning-login router | domain/auth/ReturningLoginRoute.kt |
done | M1 |
| Auth advance key | domain/auth/AuthAdvanceKey.kt |
done | M1 |
| iOS path | Android target | Status | Notes |
|---|---|---|---|
Components/PrimaryButton.swift |
designsystem/component/PrimaryButton.kt |
done | |
Components/Pill.swift |
designsystem/component/Pill.kt |
done | |
Components/CardView.swift |
designsystem/component/CardView.kt |
obsolete | No product call site — reached only from ComponentGallery, which is itself unreachable. Dead code; deletion tracked in #181 |
Components/HRule / Section |
HRule.kt |
partial | |
Components/ArtistTile.swift |
designsystem/component/ArtistTile.kt |
obsolete | Landed in M2, trimmed in 3469d09, and no call site since DS: Discover draws Tile.kt, Search its own OccasionCard/ResultCard/ArtistThumb, Artist list its own ArtistThumb. Carries raw 192/252dp and Color.White; deletion tracked in #181 |
Components/MediaContainer.swift |
designsystem/component/MediaContainer.kt |
obsolete | Ported in 6e12b3a (M2b), softened in 1a49277, then deleted in 3469d09 ("resolve the three components nothing called"). Media slots are drawn by Tile.kt's MediaSlot and by the private ArtistThumb/cover composables in feature/search and feature/profile — three copies of one cover, which is #177 |
Components/EmptyStateView.swift |
designsystem/component/EmptyState.kt |
done | M2 |
Components/Avatar.swift |
designsystem/component/Avatar.kt |
done | DJB2 hue + initials |
Components/Skeleton.swift |
designsystem/component/Skeleton.kt |
done | |
Components/ScoreRing.swift |
designsystem/component/ScoreRing.kt |
obsolete | Ported with New-tier nil handling, but it has had no call site since the Sep-2026 redesign — feature/score draws its own ScoreDonut. Dead code, and the only raw literals in that section (64/6dp, 1.2sp); deletion tracked in #181 |
Components/Sparkline |
designsystem/component/Sparkline.kt |
obsolete | No call site. The studio's trend is a hand-rolled 4.5dp meter, not this. Dead code (raw 48/3dp); deletion tracked in #181 |
Components/HeaderBar.swift |
designsystem/component/Headers.kt (ScreenHeader / BackHeader) + feature/booking/BookingChrome.kt (FunnelBar) |
partial | Tab roots use ScreenHeader (6 production call sites), pushed screens BackHeader (25), the funnel FunnelBar + FunnelStepBar (4) — counts exclude previews and the unreachable gallery. Deliberately not a Material TopAppBar. ScreenTitleBar.kt and FunnelHeader are dead; the row named both as the live targets until Sep 2026. partial because the three headers ship three bar heights and two subtitle steps, and the 40dp iconCircleSm backs roughly 13 of its 36 uses as a header back control where §2 says 42 (#166) |
Components/Haptic.swift |
designsystem/Haptics.kt |
done | 7 verbs + rememberHaptics(). Fired at 24 of iOS's 26 non-signup sites. ReportArtistSheet closed its two on the Sep-2026 AP redesign (reason tap = selection, submit = warning — not success: the app must not congratulate anyone for filing a complaint). The 2 remaining gaps have no Android surface: per-category review dots, and the thread report picker, which files on the reason tap so there is no separate select moment |
| Theme tokens | designsystem/theme/* |
redesigned | Sep 2026 — the dark, dual-accent language is retired. docs/REDESIGN_2026-09.md is the token sheet (§2 palette/type/geometry, §4 old→new mapping). Light surfaces, ONE lime accent for both roles (withRole is identity), Plus Jakarta Sans + JetBrains Mono in res/font/; the editorial serif is gone and SerifFamily is a deprecated alias of the sans. Old AppType/AppColors names survive as aliases so inherited screens compile — the eleven P2 section PRs retire them. Over-media chrome (glass*, inkOnMedia*, ArtistGradient) is deliberately unchanged: photos are still photos |
| Component library v2 | designsystem/component/* |
done | PrimaryButton, SecondaryButton, IconCircle, SearchBar/SearchBarButton, Chip/ChipRail, SectionHeader/EyebrowLabel, Tile/MediaSlot, HeroCard, ListRow, Banner (absorbs InlineBanner), StatusPill, EmptyState, Skeleton*, Toast/ToastHost, LightTabBar, ScreenHeader, BackHeader, SheetScaffold, AppTextField, OtpField. Landed in P1 with previews and no call sites; the P2 sections wired them and added their own (DetailHeader, EventTimeline, BottomActionBar, AccentNote, ClockColumn, GroupLabel, SendingNarration, DashedSlot, TrustedTick, Meter) |
| Month calendar | designsystem/component/MonthCalendar.kt |
redesigned (BN) | Design 78 documents all five day states and this implements them: MonthDayFill (Booked · Unavailable · Open · Selected) plus today's ring, with monthDayFill as the pure precedence rule. Adds MonthCalendarCard, MonthCalendarLegend, DayEventRow and ClockColumn — the stacked "8:00" over "pm" time column designs 36 and 78 draw identically, shared with the Gigs day list. MonthDayGrid keeps its signature for Gigs and gains one optional unavailableDays |
| Section SH primitives | designsystem/component/{Toast,SectionHeader,LightTabBar}.kt |
extended (SH) | ToastHost takes a key (identity of the current message, so a repeated string restarts the timer); GroupLabel joins EyebrowLabel — the design sets an eyebrow in JetBrains Mono and a list's group divider ("TODAY", "ALSO FIXED") in the sans at 12.5/700, and a run of mono dividers makes a scrolling list read as a log file; lightTabBarHeight() exposes the bar's height from its own tokens for the one caller that must clear it without being laid out against it. The host above the NavHost is the ONLY one — a feature raises a toast by injecting the @Singleton ToastController into its ViewModel (ArtistProfileViewModel does), never by mounting a second host. feature/epk still has one, and is the outstanding migration |
| Section BN primitives | designsystem/component/{DetailHeader,EventTimeline,BottomActionBar,AccentNote}.kt |
new (BN) | Left-aligned record header (18/78/83/84/95/96/97/117), the light design's 11dp-dot timeline, the pinned bottom bar (not dockSurface — no rounded corners on the page's own edge), and the lime-washed aside six BN screens share |
| Global chrome | designsystem/component/LightTabBar.kt |
done (P1, +AC) | Replaces the floating blurred pill. Opaque, hairline top, four glyphs + a raised accent action circle. Client: Home · Search · [+] · Messages · Profile. Artist: Studio · Gigs · [+] · Messages · Profile. FloatingTabBar and AmbientRoleWash are deleted. Sep 2026 (AC): an opt-in showLabels draws the glyph names for Accessibility's "Always show labels"; off by default |
- Operator Google/Apple dashboard config +
google-services.json/ FCM server path - Artist profile PROF-10 hero pager (review search/sort shipped Sep 2026 as
ArtistReviewsScreen)
When a screen/repo lands: flip Status to done or partial, add the Android path,
and note any Jul-2026 deltas. Keep this file honest — stale rows hurt more than
missing ones.