Files
web-terminal/ios/App/WebTerm/DesignSystem/Typography.swift
Yaojia Wang 284cfd193a feat(ios,android): P2 wave, git panel, token UX, per-host WS token, docs
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.
2026-07-30 15:58:01 +02:00

82 lines
3.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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())
}
}