docs(apple): drop [FIXED] tags from the plan
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Has been skipped
CI / conformance (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Has been skipped

Decisions stay recorded; the tag implied false permanence. Open items
keep their [OPEN / TRACKED] / [GATE] markers.
This commit is contained in:
Ilia Denisov
2026-07-15 19:24:45 +02:00
parent ebc413f5e5
commit e04e97fb66
+24 -29
View File
@@ -4,8 +4,8 @@ Native **SwiftUI** application for iPhone and iPad, written **from scratch** (no
wrapper, unlike the Android app under `ui/android`). This document is the **source of truth and
continuity** for the effort: it lives in the repo so it travels to whatever machine does the work —
in particular the **Mac Claude session** that does the actual building. Read it first; append to it
as decisions land. Nothing here is final until built and verified; where a decision is locked it is
marked **[FIXED]**.
as decisions land. Nothing here is final until built and verified; any decision can be reopened.
Items still open carry an **[OPEN / TRACKED]** or **[GATE]** marker.
Chat with the owner is in Russian per the user-level guide; this doc, like all repo docs, is in
**English**.
@@ -62,8 +62,7 @@ simulator installed generally; the steps below are mostly **verify**, a few are
---
## 2. Fixed decisions [FIXED]
## 2. Fixed decisions
| Topic | Decision |
|---|---|
| Language / UI | Native **SwiftUI**, from scratch; **not** Capacitor. |
@@ -125,8 +124,7 @@ custom build phases, so custom needs are expressible; worst case escape hatches
---
## 5. Determining optimization principles [FIXED]
## 5. Determining optimization principles
These five shape the architecture from the start (not "add later"):
1. **Push (APNs) instead of a live socket while backgrounded.** For a turn-based game we do **not** hold
@@ -159,8 +157,7 @@ These five shape the architecture from the start (not "add later"):
---
## 7. Storage — persist eagerly, do not trust `willTerminate` [FIXED]
## 7. Storage — persist eagerly, do not trust `willTerminate`
There is **no reliable destructive callback** on iOS: `willTerminate` is often **not** called (swipe
force-kill, memory kill happen silently). Relying on catching a "destroy" event is a trap.
@@ -191,8 +188,7 @@ there — they need the **codec unit tests**.
---
## 9. Crash reporting & analytics [FIXED]
## 9. Crash reporting & analytics
- **Crashes: MetricKit.** `MXMetricManager` delivers crash/hang/CPU/disk diagnostics to the app; we
**upload the payload to our own backend** and view it in the existing **Grafana/logs** contour — fully
self-hosted, zero third-party, no paid service. Apple's Xcode Organizer / App Store Connect also provides
@@ -354,8 +350,7 @@ with the game screen (the vertical slice).
· `ShareLink` / share sheet (export) · **TipKit** (coachmarks) · grouped `List` (settings/legal) · SIWA
native sheet (auth).
### Assets, tiles & icons [FIXED]
- **Tiles are drawn as runtime vectors** (a rounded rect + a font glyph + the point value, on the board
### Assets, tiles & icons- **Tiles are drawn as runtime vectors** (a rounded rect + a font glyph + the point value, on the board
`Canvas`) — **no image assets, no pre-generation, no local-store cache.** Resolution-independent (crisp
under zoom-on-drop), theme/variant/locale-adaptive for free. Cache rendered tile images
(`ImageRenderer`) **only if** profiling later shows a need. The crossword loading splash **reuses the
@@ -374,14 +369,14 @@ native sheet (auth).
#### F. Game screen — detail
**Layout [FIXED]**
**Layout**
- **iPhone: full board always visible + zoom-on-drop** (hybrid, web-parity, setting-gated by the
existing "Zoom the board" toggle). Board scaled to width; on tile drop it magnifies toward the drop
point for precision.
- **Bottom-anchored stack**, top→bottom: *(top strip — the ad banner (L) + connection chrome, else
empty)* → **seats header****board****rack****bottom toolbar**. Everything is packed to the
bottom; the top strip hosts the L banner / Connecting…/Offline/connection-lost chrome, else empty.
- **Orientation [FIXED]: iPhone portrait-only; iPad portrait + landscape.**
- **Orientation: iPhone portrait-only; iPad portrait + landscape.**
**Four regions**
1. **Scoreboard / header** — seats (name, score, whose-turn), unread dot (red msg / amber nudge),
@@ -426,7 +421,7 @@ native sheet (auth).
resign/add-friend/block/chat hidden; a started move stays as a draft); if already in chat/dictionary
you stay (chat read-only, dictionary keeps checking on-device).
**UX decisions [FIXED]**
**UX decisions**
- **Comms sheet:** a **single detented sheet** with a segmented control **[History | Chat |
Dictionary]**; the board + seats header stay visible above; native swipe-to-dismiss + grabber.
**Opened by tapping the seats header** (where the unread dot / 💬 live). iPad landscape later adapts
@@ -464,7 +459,7 @@ emerge while implementing the vertical slice, step 3.)*
#### D. Lobby — detail
**UX decisions [FIXED]**
**UX decisions**
- **List style:** native **inset-grouped `List`** (the Settings-app look — floating rounded section
cards over a grouped background). Sections: **your turn / opponent's turn / finished** (empty hidden).
- **Grouping (option A — unified lobby):** one lobby, all games in the three state sections. **Local
@@ -499,7 +494,7 @@ emerge while implementing the vertical slice, step 3.)*
#### E. New Game — detail
**UX decisions [FIXED]**
**UX decisions**
- **Structure:** a top **segmented `[Quick game | With friends]`** + a contextual **inset-grouped form**
below (consistent with the lobby aesthetic).
- *Quick game:* opponent segmented **`[AI | Random]`** → variant (emblem chips) → RU "multiple words"
@@ -525,7 +520,7 @@ emerge while implementing the vertical slice, step 3.)*
multiple-words · **move timeout** (5 min24 h, default 24 h) · **hints** (allowed? how many each) ·
**leaver's-tile disposition** (returned to bag / removed — 34-player games only).
**UX decisions [FIXED] (sub-details)**
**UX decisions (sub-details)**
- **Cap 🔒 prompt:** a 🔒 badge on the **Start** control (looks unavailable); a tap opens a native
alert — guest: a "Sign in / Create account" action; signed-in: an informational "finish a current
game first."
@@ -540,7 +535,7 @@ emerge while implementing the vertical slice, step 3.)*
#### C. Login / identity — detail (v1: SIWA + email + guest)
**UX decisions [FIXED]**
**UX decisions**
- **No hard login wall — guest by default** (offline-first, as Android native). First launch drops
straight into the lobby as a guest.
- **Reusable sign-in sheet:** a bottom **detented sheet** (same idiom as the comms sheet) with the
@@ -557,7 +552,7 @@ emerge while implementing the vertical slice, step 3.)*
#### B. Onboarding — detail (TipKit, native)
**UX decisions [FIXED]**
**UX decisions**
- **TipKit, native flavour** (confirmed): contextual tips anchored to controls, ordered via **`TipGroup`**
**no full-screen dimming, no tap-anywhere-advance**; each tip dismisses itself (its ✕ or performing
the action). Shown once; per-device eligibility.
@@ -568,7 +563,7 @@ emerge while implementing the vertical slice, step 3.)*
#### G. Friends — detail (Settings → Friends)
**UX decisions [FIXED]**
**UX decisions**
- **Structure:** a single **inset-grouped `List`** with sections **Requests** (incoming: accept /
decline; outgoing: cancel) · **Friends** · **Blocked** (unblock). A toolbar **"+"** opens the add
sheet.
@@ -587,7 +582,7 @@ emerge while implementing the vertical slice, step 3.)*
#### H. Profile & settings — detail
**UX decisions [FIXED]**
**UX decisions**
- **Settings hub** = an icon **`List` of drill-down `NavigationLink` rows** (the Podcasts → Library
pattern), SF-symbol icons, with room left at the bottom for optional general info later. Rows:
- **Interface** (⚙️) → display-settings screen.
@@ -602,12 +597,12 @@ emerge while implementing the vertical slice, step 3.)*
- **Interface screen** (display prefs): language · theme · board bonus-label style · reduce-motion ·
zoom-the-board.
**Guest has no editable settings** [FIXED] — the random nick is not changeable, the variant is always
**Guest has no editable settings** — the random nick is not changeable, the variant is always
`erudite_ru`, the timezone is auto-detected (never set by hand). So there is **no guest profile
screen**: the profile icon for a guest opens the **sign-in sheet** (confirms C). It also confirms E's
"variant picker hidden when a single variant is enabled" for guests.
**Profile screen (signed-in / durable)** [FIXED] — an inset-grouped `Form`:
**Profile screen (signed-in / durable)** — an inset-grouped `Form`:
- **Name** — inline `TextField` + validation (letters + `·`/`.`/`_` separator, optional trailing `.`
or ≤5 digits, ≤32 chars, ≤5 specials).
- **Play** — timezone (UTC-offset picker, pre-filled from the device) · away window (10-min grid, ≤12 h,
@@ -622,7 +617,7 @@ screen**: the profile icon for a guest opens the **sign-in sheet** (confirms C).
#### I. Wallet & store — detail (durable only; delivery staged)
**UX decisions [FIXED]**
**UX decisions**
- **Structure:** inset-grouped — a **balance card** (running-context «Фишка» first, then linked
other-platform chips behind their logos: VK / Telegram / web / **Apple** as a new context) → **Active**
line (hints remaining + ad-free end date / "forever"; hidden when empty) → segmented **`[Buy | Spend]`**
@@ -644,7 +639,7 @@ screen**: the profile icon for a guest opens the **sign-in sheet** (confirms C).
#### J. Feedback — detail (durable only; guests hidden)
**UX decisions [FIXED]**
**UX decisions**
- inset-grouped `Form`: **`TextEditor`** (≤1024, live counter) → attachment row → **Send**.
- **Attachment:** one file ≤1 MB (images / PDF / text-log / office / RTF / archives). **"Attach"** →
menu **`PhotosPicker` (Photos)** / **`fileImporter` (Files)**; **no camera**. An unsupported file is
@@ -658,7 +653,7 @@ screen**: the profile icon for a guest opens the **sign-in sheet** (confirms C).
#### K. Info / legal — detail
**UX decisions [FIXED]**
**UX decisions**
- **About screen** (Info hub row): app name · **version** (native build + the client-version) · links to
the three legal docs · copyright; optional "Rate on the App Store".
- **Legal docs** (EULA `/eula/`, privacy `/privacy/`, offer `/offer/` + live price list): open via
@@ -668,7 +663,7 @@ screen**: the profile icon for a guest opens the **sign-in sheet** (confirms C).
#### A. Boot / launch & update — detail
**UX decisions [FIXED]**
**UX decisions**
- **LaunchScreen:** static, OS-level, instant (**iOS cannot animate the launch screen**) — brand/logo.
- **In-app loading:** the crossword-tiles splash (ЭРУДИТ/ЗАГРУЗКА/ОЖИДАНИЕ) shows **only while the lobby
list isn't ready**; fast silent boot → usually straight to the lobby. Offline cold launch → straight to
@@ -684,7 +679,7 @@ screen**: the profile icon for a guest opens the **sign-in sheet** (confirms C).
#### L. Advertising banner — detail (cross-cutting component)
**UX decisions [FIXED]**
**UX decisions**
- **Placement:** a one-line strip **on the main tabs (Lobby / New Game / Statistics) AND in-game (F)**
players spend most time in-game. Hidden in settings drill-downs and modal flows. **In-game it occupies
the top strip** (the empty space above the seats header), coexisting with the connection chrome