App layer, four sequential slices (a shared .xcodeproj means adding files regenerates it, so these could not run in parallel): - token UX end to end: pairing prompts for a token when a host 401s, POST /auth validates it, and 204-without-Set-Cookie is correctly read as "this server has auth disabled" rather than "authenticated". A host paired before the token was turned on recovers by re-pairing in place. Remove-host now exists and finally gives PushRegistrar.handleHostRemoved a caller. - project git panel + worktree lifecycle (T-iOS-32) + claude --resume history — the parity gap with Android and the web front end. - terminal search (T-iOS-33) and voice PTT (T-iOS-31) with an epoch guard so a session switch between dictation and confirm cannot inject into the wrong session. - theme + Dynamic Type (T-iOS-34) and web ?join= interop (T-iOS-35). RootView no longer hard-locks .preferredColorScheme(.dark). Also unpins SwiftTerm to 1.15.0 by dropping the local hasActiveSelection that collided with the upstream one, verified green from a fresh derivedDataPath. Includes the two HIGH fixes the security review found: - iOS resolved the WS token host-independently, so a token-gated host sitting next to an open one could never open a terminal and no on-screen remedy could fix it. Now one transport per host; cross-host leakage is structurally impossible since both read paths return only that host's own value. - Android reported the host's own git-credential 401 (git-ops.ts:108, "Push authentication required on the host.") as "your access token is wrong", because a blanket 401 mapping ran ahead of the per-route one. Git-write routes are now ROUTE_DEFINED and keep the server's message. And the doc sync: README/ios README no longer claim the client is unmerged on feat/ios-client, the Clients section finally lists Android, and the plan checkboxes reflect what is actually built. iOS 534 app tests + 452 package tests; Android 687 tests.
82 lines
3.5 KiB
Swift
82 lines
3.5 KiB
Swift
import SwiftUI
|
||
|
||
/// # Typography — the SF type ramp (FROZEN public surface)
|
||
///
|
||
/// All font choices come from `DS.Typography`. The ramp maps to Apple's
|
||
/// semantic text styles so everything scales with Dynamic Type (the a11y
|
||
/// audit's "keep it scalable" point). Numbers/dimensions/cost/`cols×rows`/
|
||
/// timestamps use `mono(_:)` — SF Mono with tabular figures — so columns line
|
||
/// up and digits don't jitter as values change (matches the existing
|
||
/// `TelemetryChips`/`Timeline` convention, now centralized).
|
||
extension DS {
|
||
enum Typography {
|
||
|
||
// ── Proportional ramp (Dynamic-Type scaling, semantic styles) ───────
|
||
|
||
/// Screen hero title.
|
||
static let largeTitle = Font.largeTitle
|
||
/// Section / prominent title.
|
||
static let title = Font.title2
|
||
/// Emphasis / card heading.
|
||
static let headline = Font.headline
|
||
/// Default body text.
|
||
static let body = Font.body
|
||
/// Slightly smaller body (secondary actions).
|
||
static let callout = Font.callout
|
||
/// Meta / caption text.
|
||
static let caption = Font.caption
|
||
|
||
// ── Monospaced (numbers, dimensions, timestamps) ────────────────────
|
||
|
||
/// SF Mono + tabular figures at the given text style (default `.body`).
|
||
/// Use for anything numeric that must align or not jump: `cols×rows`,
|
||
/// device/client counts, `$cost`, context %, relative timestamps.
|
||
static func mono(_ style: Font.TextStyle = .body) -> Font {
|
||
.system(style, design: .monospaced).monospacedDigit()
|
||
}
|
||
|
||
/// The canonical meta-number font: caption-sized mono + tabular.
|
||
static let metaMono = mono(.caption)
|
||
|
||
// ── Dynamic Type clamp for dense numerics (T-iOS-34) ────────────────
|
||
|
||
/// Upper bound applied to **numeric / meta** text only (telemetry chips,
|
||
/// `dsMetaText` rows: `ctx 92% · $0.1234 · 161×50`).
|
||
///
|
||
/// Prose scales all the way to `.accessibility5` — that is the point of
|
||
/// Dynamic Type and nothing here caps it. Tabular numerics are different:
|
||
/// they live in one-line rows next to a status badge, and at AX4/AX5 a
|
||
/// single chip row becomes three wrapped lines that push the row's real
|
||
/// content off screen. Capping them at `.accessibility2` (already ~2×
|
||
/// the default size) keeps the meta line legible AND keeps the row's
|
||
/// primary text — the session name — visible.
|
||
///
|
||
/// Pinned in `DynamicTypeLayoutTests` both as a policy value and
|
||
/// behaviorally (AX2 and AX5 must measure the same height).
|
||
static let numericClamp: DynamicTypeSize = .accessibility2
|
||
}
|
||
}
|
||
|
||
// MARK: - MetaText style
|
||
|
||
/// Secondary + monospaced-tabular styling for a row's numeric meta line
|
||
/// ("N 台设备在看 · 161×50"). Apply via `.dsMetaText()`.
|
||
///
|
||
/// T-iOS-34 · carries the `numericClamp` so every meta line in the app gets the
|
||
/// same Dynamic Type ceiling from ONE place (per-screen clamps would drift).
|
||
struct MetaText: ViewModifier {
|
||
func body(content: Content) -> some View {
|
||
content
|
||
.font(DS.Typography.metaMono)
|
||
.foregroundStyle(DS.Palette.textSecondary)
|
||
.dynamicTypeSize(...DS.Typography.numericClamp)
|
||
}
|
||
}
|
||
|
||
extension View {
|
||
/// Style a meta/number line as secondary monospaced-tabular text.
|
||
func dsMetaText() -> some View {
|
||
modifier(MetaText())
|
||
}
|
||
}
|