Character · Evidence · States · Text · Colour · Surfaces · Shape & space · Buttons · Symbols · Components · Placement · Motion · Haptics · Words · Accessibility · Building a screen · Conflicts

Design System

The rules every Anchor Alert screen follows. They are read out of the app as it ships, so where the code and this page disagree, the code is what skippers see and this page is the intent.

The rules that already have a home win over this page: the palette and the invariant in AGENTS.md, the words in Docs/copy.md. Tokens live in AnchorAlert/Helpers/: MagicValue.swift for sizes, AppButtonStyles.swift for buttons, BrandChrome.swift for the bar; colour is AnchorAlertWidgets/BrandPalette.swift, drawn on the Palette page. Where the app is inconsistent, the rule here is the choice to build to. Amber boxes mark the conflicts in place, and they are all together at the bottom.

Character

An anchor alarm for a phone below deck at 03:00. One screen: a map with the anchor, the circle and the boat, a bar across the top, and a glass panel at the bottom that answers "are we dragging?" without a tap. Everything else is a sheet over it. The app is locked dark (AppMain.swift, #207): the bar, the menu circle and the banner are MarNav's chrome grey, and a light page under them read as two apps stitched together. The chart under it keeps its own style (onDay, onNight or onCHART).

Glanceable at 3 a.m.

The distance is the biggest thing on the screen, and the word beside it says what it means: Holding, Checking, Dragging, Can't judge.

Calm until it isn't

Green all night, amber when something is worth a look, red only for the two minutes you are not asleep. Red is never decoration.

Rings by itself

Nothing on the screen waits on a network, an account or a reply. The paywall gates starting a watch and never touches a running one.

An alarm needs evidence

The rule that shapes the most screens (AGENTS.md). Silence is not evidence: when an observation has an innocent cause and a fatal one, the app reports it and says what it doesn't know, rather than acting on a guess.

Unknown is its own state. "Can't judge" is grey, not green and not red. With no fix, the panel never says Holding. .unknown is first-class in SystemsCheckStatus, not a synonym for failing.
A drag is a verdict, not a fix. "Checking" fills an amber ring while most recent fixes are outside the circle; only a sustained majority makes it Dragging. One stray fix moves nothing.
Say why it won't ring. The slide's subline names the reason the siren is held back: "Won't ring: GPS can't judge a drag", "Rings unless the boat comes back".
Faults push. A failing Systems Check turns the bar's shield red and posts a notification. A warning that waits to be read is a warning nobody reads.

The watch's states

AnchorWatchStatus names them, in AnchorAlertWidgets/ so the Live Activity draws the same set. Each has a title, a glyph, an ink and a Live Activity swatch.

StateColourGlyphPanelHaptic
No anchorgreymappin.slash"Mark anchor" in green, or amber when a tap will explain rather than act (no fix yet, no subscription)success on mark
Holdingholdingcheckmark.circle.fillGreen dot "Holding"; the slide arms the alarmmedium on arm
Checkingattentionring + exclamationmarkAn amber ring filling toward the sirenmedium
Can't judgegreylocation.slash.fillNo usable fix: never Holdingmedium
Draggingdangerexclamationmark.triangle.fillRed "Dragging"warning
Alarmdanger fillbell.and.waves.left.and.right.fillBar goes red and throbs "Alarm"; Snooze appears; the slide goes red, "Slide to turn alarm off"the siren's buzz, every 0.2 s
Snoozedattention—Amber slide, "Snoozing m:ss"; no case of its ownnone
Alarm offattention inkbell.slash.fillAnchored, not armed: the green slide says "Slide to turn alarm on"warning on disarm
Anchor marked, alarm off
Marked, alarm off
Armed and holding
Armed, Holding
Location off, Can't judge
Can't judge
The alarm ringing
Alarm
Snoozed
Snoozed
Conflict. Two classifiers for one boat. The badge, the progress bar and the slide's subline read determinePosition (the alarm left out); the map ring, the haptic and the Live Activity read determine (the alarm folded in). With the alarm off and the boat outside the circle, the badge says amber "Checking" while the ring stays green, and the status haptic never fires while disarmed, which DetermineStatusHaptic's own doc says it should. Build to: position for the badge, bar, ring and haptic; the alarm only on the surfaces that are about the alarm (the bar, the slide).
Conflict. Snoozed has no AnchorWatchStatus case. While the panel shows amber "Snoozing", the Lock Screen and Dynamic Island show red "Dragging" with the red wash. Build to: a .snoozed case, attention, zzz (Always on's symbol for snooze already).
Conflict. "Alarm off" is green on the map ring, amber as ink, orange in the Live Activity swatch, and the badge never shows it. Build to: one meaning. The ring's comment argues for green (the anchor holds; only the alarm is off), so make the ink and swatch match, and drop the unused glyph.

Text

System text styles, so text follows Dynamic Type. The distance readout is the one fixed size, because a number that shrinks at night is a number you can't read across the cabin. Samples are at the default text size.

RoleSampleStyleFor
Distance23 m48 pt SF Rounded bold, mono digits (36 when space is short); unit Title 3 bold, secondaryThe panel's readout
Radius30 mTitle 3 semibold, rounded, mono digitsThe alert radius between − and +
Control titleAlert radiusTitle 3 semiboldRadius label, the slide's title
VerdictHoldingSubheadline, primary, one lineThe badge beside the distance
ReadingGPS ±5 m · just nowCaption 2, monospaced, secondaryThe instrument line under the distance
Sheet heroRelease the anchor?Title, semiboldThe question a decision sheet asks
Onboarding titleAllow locationLarge Title, boldFull-screen onboarding pages only
Row titlePositionHeadlineSystems Check domains, log rows, menu rows
Card headerIf the alarm firesSubheadline semibold, secondary.sheetCardHeader()
DetailFixes every second, ±5 mSubheadline, secondarySecond lines
NoteTurn the alarm off to release the anchorFootnote, secondary, centredThe line under a button
CaptionToday, 21:40Caption, secondaryTimestamps, sub-checks
Numbers that change use mono digits. Distance, radius, the replay clock and every countdown take .monospacedDigit() so they don't jitter as they tick.
Rounded is for the two panel numbers. Distance and radius; the reading line under them is monospaced, an instrument rather than a headline.
One line, then two at accessibility sizes. Titles that can't wrap at the default size switch to two lines and a smaller scale factor when isAccessibilitySize, rather than truncating.
No uppercase. Nothing sets textCase, and headers are sentence case. The log's "ALARM" is the one deliberate shout.
Conflict. Sheet heroes are titled four ways: Title semibold (Mark, Release), Title bold (Menu, Subscription), Large Title bold (About, onboarding), Title 2 semibold (Remote). The app's name is Large Title in About and Title in the menu. Build to: Title semibold for a sheet; Large Title bold only for a full-screen onboarding page.
Conflict. The distance is SF Rounded bold in the app and default-design Title 2 bold in the Live Activity. Times in the log tick in a TimelineView without mono digits. Build to: rounded for the distance wherever it is drawn; mono digits on every time that ticks.

Colour

Every colour is a role, and every role is on the Palette page with its contrast. The three rules from AGENTS.md:

A colour on a colour is a named pair. A fill travels with the label that reads on it (BrandSwatch). Nothing outside the palette hands a view a Color.
Ink on the page is BrandInk. A glyph, dot or line has no background of its own, so it is the one part of the palette that flips with the appearance (only the dark side ships). A fill used as ink is the #30 bug.
Surfaces name a role, never a colour. AppButtonTone, PanelCapsuleTint, BrandChrome, AnchorWatchStatus.color, SystemsSummary.tint.
Link blue commits you to nothing. neutral is Continue, Test alarm, Done and the map's scale rings. Green starts a watch; red ends one or throws work away.
#1C7B5A
holdingThe brand, and the state the app is in all night
#F5A524
attentionWorth a look, nothing on fire
#A1281B
dangerDragging, the alarm, ending a watch
#5B9BD5
neutralonNAV link blue; the accent
#373340
chromeThe bar, the menu button, MarNav's grey
#D93A2B
dangerOnChromeThe failing shield on the bar
Conflict. Half the palette's machinery serves an appearance the app never shows. BrandInk flips per appearance, BrandPaletteTests measures both, and AGENTS.md asks for every pair to be legible "in both appearances", but since #207 the app is locked dark, so the light side is drawn only on canvases. Build to: say so in AGENTS.md and in BrandInk's doc (dark is what ships; light is kept for previews), or retire the light half.
Conflict. Fills drawn as ink, against the rule even where they happen to pass. ButtonRow's destructive glyph is danger.fill (red4) on a dark list cell: 1.9:1, the #30 failure again, under a comment arguing the bare colour is allowed. A Remote contact's warning line, the paywall's checkmarks and onboarding's icons use fills too; they pass on dark today only because the app is locked dark. Build to: BrandInk for every one.
Conflict. The Live Activity uses iOS's green, orange and red with white labels, on purpose (documented in AnchorWatchStatus): the brand steps sink on black. But white reads at 2.0 to 2.2:1 on that green and orange, and nothing measures it. Its stale pill is a raw Color.orange, and its radius bar stays green under red "Dragging", the mistake the in-app bar's doc says it fixed. Build to: keep the system hues, give green and orange a dark label, measure the swatch in the tests, and colour the bar by status.
Conflict. Red means more than the drag. The boat's track is danger red on the live map and in the replay, with no drag behind it. The replay draws the alert ring in scale-ring blue at 3 pt where the live map uses the verdict colour at 6 pt. "Can't judge" is three greys (.systemGray, .secondary, .gray). Build to: a quiet track colour, the live ring's style in the replay, one grey token.
Conflict. The palette's comments disagree with what ships. BrandRamp.red5 says it is "the one that ships"; danger is red4, and the Palette page shows red5 unused. BrandChrome and PanelCapsuleTint call red4 "Alarm Red", the sheet's name for red3. Color+App describes scaleRing as the anchor's travel line; it draws the anchor-to-boat line. Build to: fix the comments; red4 is what ships.

Surfaces

Map

Mapbox, full bleed, the anchor, the circle and the boat. Everything else floats over it or slides up over it.

Bar · BrandBar

Opaque chrome grey across the top, MarNav's 76 pt circle menu button hanging off its leading edge, shortcuts, the wordmark trailing. Always dark; red and throbbing while the alarm rings.

Panel · MainPanel

Regular .glassEffect, continuous radius 20, inside a GlassEffectContainer. Bottom of the screen in portrait, a 360 pt side column when wide.

Sheets

On systemGroupedBackground. Card pages use SheetCard (radius 18, the same as an inset-grouped list); settings-like pages are a Form or List.

Glass over the map, solid on a page. Glass is for things that float over the chart. A sheet is a flat page, so its buttons are solid fills (the reason brandProminent exists).
Glass needs a hit area. glassEffect adds none: a glass button without .contentShape is tappable only on its letters. That cost a tester his anchor release.
Conflict. Three sheet models: hand-built SheetCard pages (Systems, Always on, Release), system Form/List (Settings, Log, Remote), and menu-row cards at radius 8 (Menu, About). They have different headers, separators and corners, and MagicValue.cellCornerRadius exists only to make 18 match a list. Build to: inset-grouped lists for settings-like and log content; SheetCard only where rows need custom disclosure, with one header style.
Conflict. Decision buttons on sheets are tinted glass (.onboarding on Mark, Release and Subscription) although brandProminent's doc says a sheet is a flat page and should be solid. Tinted glass shifts the fill, so the contrast measured on the palette doesn't hold. Build to: solid on every sheet; glass only over the map.
Conflict. The app icon is a 72 pt rounded square in onboarding and an 80 pt circle in About, the menu and Subscription. The panel's radius 20 borrows largishPadAmount, a padding token. Build to: one icon shape and size token; a named panelCornerRadius.

Shape & space

Capsules and circles float; cards are rounded rectangles. Every button, pill and the slide's track is a capsule or circle. The panel is 20, sheet cards 18, menu rows 8, all continuous.

Spacing

MagicValue's scale. Plain .padding() (16) stays plain.

tinyPadAmount · 2
smallPadAmount · 5 — the most used
mediumPadAmount · 10
mediumPlusPadAmount · 15
defaultPadAmount · 16 — the system's
largishPadAmount · 20
largePadAmount · 30

Sizes

TokenPointsFor
minimumTapTargetSize44Nothing tappable is smaller (but see the conflict)
mainMenuButtonSize · brandBarMinHeight76 · 40The circle menu button over the bar
panelCapsuleMinHeight54The alarm slide's track
sidePanelWidth360The panel as a column, landscape or iPad
distanceReadoutFontSize48 · 36The distance, and when space is short
pressedOpacity · dimOpacity75% · 25%A pressed button; no reading yet
Conflict. Two grids. The app counts 2, 5, 10, 15, 20, 30 beside the system's 16; the Live Activity and the pills use 2, 4, 8. Padding tokens double as sizes (the panel radius, the Systems icon column, the volume slider's height). Menu rows press to 60%, everything else to 75%. Build to: one 4-point scale that includes 16, separate size tokens, pressedOpacity everywhere.
Conflict. Targets under 44 pt: every sheet's Done disc (34), the bar's wordmark (26 tall), the Systems Check's remedy links (about 20). Build to: keep the drawn size, make the hit area 44 with a frame and contentShape.

Buttons

AppButtonStyles.swift. A style is a shape and a size; an AppButtonTone (.primary, .start, .attention, .danger, .inactive) picks the pair.

StyleLooksFor
.onboarding(tone)Title 3 semibold, full-width tinted glass capsuleThe thumb decision: Mark anchor, Release anchor, Get started
.onboardingSecondaryThe same, solid systemGray4The other answer: Not now, Keep anchor, Release anchor on the panel
.brandProminent(tone)Headline, solid capsuleA call to action inside a sheet: Test the alarm
.circular44 pt disc on the panel wash− and + on the radius
.sheetConfirm34 pt neutral disc, black checkmarkDone, on every sheet
.menuRowHeadline in the accent on an 8 pt cardMain menu and About rows
Conflict. Built but unused: .mediumPrimary, .smallPrimary, .onboardingStart, four fonts in View.swift, BrandChrome.secondaryLabelColor, PerformAlertAction.showConfirmation. The preview matrix shows styles that ship nowhere. Build to: delete them, or mark them reserved.

Symbols

SF Symbols only. One symbol per idea:

IdeaSF SymbolIdeaSF Symbol
Holding · check passingcheckmark.circle.fillSystems Check (bar)checkmark.shield.fill · exclamationmark.shield.fill · xmark.shield.fill
Check failingxmark.octagon.fillUnknownquestionmark.circle
Alarm ringingbell.and.waves.left.and.right.fillCan't judgelocation.slash.fill
Alarm on · off (slide knob)lock.fill · lock.open.fillNo anchormappin.slash
Loglist.bulletSettingsgear
Recenter (following · not)location.fill · locationShare, exportsquare.and.arrow.up
Remoteantenna.radiowaves.left.and.rightStaleclock.badge.exclamationmark
DonecheckmarkDelete · recovertrash · arrow.uturn.backward
Conflict. exclamationmark.triangle.fill means six things: Dragging, a check needing attention, a failing summary, blockers, onboarding's caution, and the palette preview. The Systems Check is checklist in the menu and a shield on the bar. A failing summary is a triangle over a failing row's octagon; unknown is filled at the top and outlined below. bell.slash.fill is both "alarm off" and "silence it". Build to: the triangle for attention only; a failing summary takes the octagon; the shield wherever the Systems Check is a door; a summary wears its worst row's glyph.

Components

Build screens from these. A new one gets a row here before it is used twice.

The main panel

MainPanel

The glass panel: DistanceToAnchorView (the readout, the reading line, AnchorWatchBadge and DragConfirmationRing), DistanceProgressView, AlertRadiusControl, AlarmSlideControl and AnchorButton. At accessibility sizes it scrolls rather than clips.

The alarm slide

AlarmSlideControl

Arming and disarming is a slide, never a tap, so a pocket can't do it. The track is a PanelCapsuleTint: green to arm, the panel wash while armed, red while ringing, amber while snoozed. VoiceOver adjusts it, and focus jumps to it when the alarm rings.

The brand bar

BrandBar · BrandBarShortcuts

Menu circle, Settings, Log, the Systems Check shield, the onNAV wordmark (About). The shield is the Systems summary at a glance; it drops its word at accessibility sizes.

Systems Check rows

SheetCard · SheetTitleRow · SystemsStatusIcon

Card pages: a grouped card, a header, rows with a status icon column, a title and a secondary detail, collapsible with CollapsibleContent. SystemsStatusIcon can only be built from a named status.

The release sheet's buttons

Decision sheet

A question as the hero, a line of consequence, and two full-width thumb buttons pinned at the bottom: the act in its tone, the way out in grey. Mark anchor and Release anchor.

Conflict. Hand-rolled copies. The Always on row rebuilds SheetTitleRow with a fixed icon column, so it drifts from the Systems Check at large sizes. The Live Activity copies the drag ring and the progress bar with literals and has drifted. Status pills are drawn four ways, status dots two, the centred icon-and-title hero eight, the thumb-button bar twice, empty states four. Build to: move the ring, bar and a StatusPill into AnchorAlertWidgets/ where both targets see them; a SheetHero and a SheetActionBar.

Placement & navigation

One screen

The map is the root. Onboarding replaces it until done; everything else is a sheet over it. No tabs, no covers, no popovers.

One sheet at a time

Every sheet is a ModalSheetKind opened through PerformModalAction, which closes the current one first.

Done is a checkmark

A neutral disc, trailing, labelled "Done". Swiping down does the same. There is no ✕ anywhere.

Decisions get a sheet

Marking when the GPS isn't ready and releasing a watch open a sheet that asks; a tap never ends a watch by itself.

Inline titles

Every navigation title is inline; no large titles.

Thumb at the bottom

The panel and the decision buttons sit where a thumb reaches in a bunk.

Conflict. Pages pushed inside Settings (Always on, Remote) have only Back, though .sheetDoneButton's own doc says pushed views need Done, and the Log adds it to its pushed session. Opening Settings at Remote lands one push deep with no Done. The contact editor uses Cancel and Add text buttons. The menu shows a grabber, About at the same detent doesn't. Build to: Done on every page of a sheet; Cancel and a verb only for a form that can be abandoned; a grabber on every sheet with two detents.
Conflict. Doors and titles disagree. The Log's door says Log and its title says "History" (or the boat's name). The Settings row says "Anchor Alert Remote", the page "Remote". The bar orders Settings, Log, Systems; its own doc and BrandBar say Log, Settings, Systems; the menu says Log, Systems Check, Settings. Build to: a title is its door's label; one order.

Motion

State changes ease

A tint, a word or a bar moving to a new state eases in and out. Disclosure is 0.25 s (MagicValue.disclosureDurationInSeconds); the map style is AppAnimations.changeMap. Numbers roll with .numericText, glyphs swap with .replace.

Only the alarm calls attention to itself

The bar throbs while ringing (AlarmPillAnimation); the menu button wiggles to be found. Both stop under Reduce Motion.

Conflict. Twelve bare .easeInOuts in views and nine bare withAnimation {} springs in Operations, so one tint change springs when an Operation drives it and eases when a view does. Reduce Motion isn't checked for the map's one-second pan on every fix, the springs in Operations, or the slide settle, which falls back to an ease rather than nothing. Build to: named curves in AppAnimations and one helper that returns none under Reduce Motion.

Haptics & sound

Two families that must not be confused (AGENTS.md). The alarm buzz is the whole-phone motor, every 0.2 s while the siren loops, off when the skipper turns Vibrate off. The interface taps are UIFeedbackGenerator, fired from Operations, never gated on Vibrate, and never the only carrier of anything.

MomentTapMomentTap
Radius stepselectionArm · disarmmedium · warning
Touch the anchor marklightDrag past the move thresholdmedium
Mark · releasesuccess · successChecking · Draggingmedium · warning
Conflict. Starting and ending a watch feel the same (both success), and Snooze, the one button pressed mid-alarm, has no tap. Where two outcomes of a gesture must be told apart, the house rule is a single impact against the warning rap. Build to: release a light impact; Snooze a medium one.

Words

The rules are Docs/copy.md; this is the short form, and what the app does against it. Every sentence a skipper reads is in a Copy/ module with a preview that draws all of it, decided beside the surface that shows it.

One word per thing

alert radius, GPS reading, notification, log, anchorage, on and off, device, Systems Check, subscription. "Fix" is the verb for a remedy, never a GPS reading; "alert" is the app's name and the radius, never a notification.

Sentence case

In the app's voice, always. Title Case only for an iOS control the skipper will go looking for (Low Power Mode, Precise Location) and screen names (Systems Check, Recently Deleted).

Label, colon, consequence

"Low Power Mode: …". No em dashes in a string a skipper reads. Fragments only where the container clips: a badge, a button, a row title, the Live Activity.

Remedy first

In the imperative, and only where the skipper can apply it. A Systems Check row is a question; the line under it answers Yes or No, and Yes is always the good answer.

Anchor Alert, two words

® on the first or most prominent use only: onboarding's welcome, About, Remote's headline, the exported log. AnchorAlert is the bundle and the code.

meters

American spelling, to match the Distance setting. The rule is consistency, not dialect.

Conflict. Case slips. "Allow Location Access" and "Allow Notifications" are Title Case buttons under sentence-case titles on the same onboarding pages, and neither is the name of an iOS control. Coordinate formats read "Decimal Degrees". "Always on" is a screen in sentence case beside "Systems Check". Build to: sentence case for the prompts and formats; decide whether Always on is a screen name and say so in copy.md.
Conflict. Words the glossary bans, or has no row for. "Alert" is also a siren sound, the log footer's "Bold marks an alert" and the disclaimer's "can all affect alerts". "Siren" is both the sound and one choice of sound. The Lock Screen card is a "live card" in Always on and a "Live Activity" in the store listing. Remote is "Anchor Alert Remote" on its Settings row and "Remote" on its page. "silent mode", "Silent Mode", "silent switch". Build to: glossary rows for siren, Remote, the Lock Screen card, Silent Mode and position, and rename the "Alert" sound.
Conflict. One state, several wordings. The Systems Check headline says "All systems OK" where VoiceOver says "All clear", "The anchor alarm isn't working" where it says "Not working", "Checking…" where it says "Not yet". An old GPS reading is "Waiting for newer GPS", "GPS reading is old" and "Wait for a new reading". A drag with no siren is "Drag with alarm off" in the log (wrong when it was a snooze) and "Dragged unalarmed" in the summary. The Settings toggle is "Notification"; the Systems Check calls it "Drag notification". Build to: VoiceOver reads the visible headline; one phrase each.
Conflict. Docs behind the app. The App Store listing says the siren sounds "at full volume"; the app says, correctly, "Device volume still applies". Only the critical notification plays at its own volume. The listing also says "Metres", "Day, night and satellite" map styles (the app has onDay, onNight and onCHART), and "every night". AGENTS.md says Remote alerts after 20 minutes of silence; the code and its docs say 30. copy.md quotes examples the app no longer says. data-flow.md names views that don't exist (summary bar, dashboard, alarm overlay). PLAN.md is wholly historical, down to the bundle id. Build to: fix the listing's volume claim before review; 30 minutes in AGENTS.md; archive PLAN.md.

Accessibility

Conflict. Two elements on the panel are both "Anchor alarm": the badge (value Holding) and the slide (value On). Systems status icons have no label, so a check's status reaches VoiceOver only as colour. Nothing is a header for the rotor. The snooze countdown isn't spoken. The Live Activity's island slots are glyphs with no labels. Build to: the badge is "Anchor"; every status icon speaks its status; card headers and hero titles are headers.

Building a screen

For agents. Before writing a view:

  1. Decide what it says when it doesn't know. Every state with no evidence looks like one; see An alarm needs evidence. Nothing in it may wait on a network to ring.
  2. Over the map, glass; in a sheet, a solid page. A new sheet is a ModalSheetKind, opened through PerformModalAction, with .sheetDoneButton on every page.
  3. Colour comes from a role: a tone, a swatch, or BrandInk for anything on the page. No hex, no .red, no fill as text.
  4. Sizes come from MagicValue; strings from a Copy/ module with a preview; values through Format.
  5. Side effects and haptics go in an Operation; the view only fires it.
  6. Every glass button ends in .contentShape; every target is 44 pt.
  7. Add a scenario to Design/screens.yml and the screen to Screens.
✕ A colour as an argumentText(warning) .font(.footnote) .foregroundStyle( BrandPalette.attention.fill)
✓ Ink on the pageText(warning) .font(.footnote) .foregroundStyle( BrandInk.attention)

Conflicts, all together

Where the app disagrees with itself today, each marked in place above with the choice to build to. None is fixed yet; a PR that fixes one takes it off this list.

  1. Two classifiers: the badge and the ring disagree with the alarm off; the haptic is silent while disarmed.
  2. No snoozed state: the Lock Screen says red Dragging while the panel says Snoozing.
  3. Alarm off green, amber and orange.
  4. Sheet hero titles four ways; the distance in two designs.
  5. Locked dark, while the ink and its tests serve both appearances; fills drawn as ink, one at 1.9:1.
  6. The Live Activity's swatch unmeasured and under 3:1; its bar green under Dragging.
  7. Red for the track, three greys for Can't judge, two ring styles.
  8. Palette comments that name the wrong red.
  9. Three sheet models; glass buttons on flat sheets; two icon shapes.
  10. Two spacing grids; targets under 44 pt.
  11. Unused styles and tokens.
  12. The triangle for six ideas; two doors for the Systems Check.
  13. Hand-rolled copies, and the Live Activity's drifted ring and bar.
  14. Done missing on pushed pages; doors and titles that disagree.
  15. Unnamed animations; Reduce Motion half-checked.
  16. Mark and release feel the same; Snooze is silent.
  17. Case slips on onboarding's buttons and the coordinate formats.
  18. Glossary gaps: alert, siren, the Lock Screen card, Remote, Silent Mode.
  19. One state, several wordings: the Systems headline, an old GPS reading, a drag with no siren, the Notification toggle.
  20. Docs behind the app: the store listing's "full volume", Remote's 20 minutes, copy.md's examples, PLAN.md.
  21. Two "Anchor alarm"s; unlabelled status icons; no headers.

Anchor Alert • Send feedback • © 2026 onNAV LLC