ios: full feature/appearance parity with the macOS app (client-side)

Port the Mac's MarkdownText renderer, AppPalette (color-vision modes +
night-softening), appearance settings (theme/text size/color vision),
and BuildBanner channel strip to the iPhone remote. Add session-row
attention/unseen-completion washes and marker icons, Discard action,
branch/worktree options in the new-chat composer, and a transient
error bubble for host wire errors. Stamp the build channel via
NucleicChannel in Info.plist (ios-release.sh passes NUCLEIC_CHANNEL,
default beta). Demo mode gains NUCLEIC_DEMO_SESSION and skips the
notifications prompt so UI previews are scriptable.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-07-02 16:06:32 -07:00
co-authored by Claude Fable 5
parent 79a28605b3
commit a33ab80313
11 changed files with 843 additions and 47 deletions
@@ -10,6 +10,10 @@ struct StartChatComposer: View {
@State private var auto = false
@State private var model: String?
@State private var effort = MobileEfforts.fallback
// Advanced options (the Mac new-session sheet's base branch + worktree fields).
@State private var showOptions = false
@State private var baseBranch = ""
@State private var useWorktree = true
private var projects: [WireProject] { store.dashboard.projects }
private var selected: WireProject? {
@@ -42,6 +46,32 @@ struct StartChatComposer: View {
ModelMenu(model: $model, catalog: store.modelCatalog, backend: nil)
Spacer()
EffortMenu(effort: $effort, catalog: store.modelCatalog, modelSKU: model, controlled: controlled)
Button {
withAnimation(.easeInOut(duration: 0.18)) { showOptions.toggle() }
} label: {
Image(systemName: showOptions ? "chevron.up.circle" : "ellipsis.circle")
.font(.caption)
.foregroundStyle(Palette.accent)
}
.buttonStyle(.plain)
}
if showOptions {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 8) {
Image(systemName: "arrow.triangle.branch").foregroundStyle(.secondary)
TextField(selected?.defaultBranch ?? "Base branch", text: $baseBranch)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.textFieldStyle(.roundedBorder)
.font(.caption)
}
Toggle(isOn: $useWorktree) {
Label("Run in a worktree", systemImage: "square.stack.3d.up")
.font(.caption)
}
.tint(Palette.accent)
}
.transition(.move(edge: .top).combined(with: .opacity))
}
HStack(alignment: .bottom, spacing: 8) {
TextField("Describe a task…", text: $draft, axis: .vertical)
@@ -51,7 +81,11 @@ struct StartChatComposer: View {
.keyboardDismissable()
Button {
if let project = selected {
store.startChat(in: project.id, message: draft, model: model, effort: effort, auto: auto)
let branch = baseBranch.trimmingCharacters(in: .whitespaces)
store.startChat(
in: project.id, message: draft, model: model, effort: effort,
baseBranch: branch.isEmpty ? nil : branch,
useWorktree: useWorktree, auto: auto)
draft = ""
}
} label: {