think
16px
820px

Obscura Mobile — design & UX audit

Date: 2026-07-27 · Build: main @ 694a2b6 · Method: 28 screens captured from the real app (web export, iPhone 13 Pro logical viewport 390×844 @2x, light + dark) against a live backend with realistic seeded content, plus a code pass over the component kit for touch targets, accessibility and state coverage.

Screens reviewed: onboarding, login, inbox, archive, folder, search (empty + results), AI, offline, document ×6 tabs, viewer, sign, e-Meterai, stamp, request-signature, share, upload, account, signatures, duty document, approval document, and three dark-mode samples.


Status — all findings fixed 2026-07-27 (85fff69)

Every item below was implemented and verified: 28 screens re-captured, full 10-suite
e2e battery green, run-r13 updated to assert the corrected duty contract and
run-r14 extended to cover internal short links.

One finding was not implemented as written: C2's "move + into the nav bar". The
concrete defect (the FAB landing on the last row) is fixed with bottom clearance, but
the FAB itself is kept — the Browse tab's header slot already carries the account
button, and a floating action button is legible on both platforms this app ships to.
That part of the finding was taste, not a defect.

Separately, the dev-login auth bypass found alongside this audit is closed in
bfeae4f and deployed — see that commit for the reasoning (the fix is an explicit
OBSCURA_ALLOW_DEV_LOGIN opt-in, because the demo must stay OBSCURA_ENV=development
to keep its dev-signed license valid).


Verdict

The app is well-built and consistent — one component kit, real design tokens, dark mode that works, honest states in the ceremonies. What it lacks is editing. Almost every screen shows everything it could show, at equal weight, so the user has to do the prioritising the interface should have done. Two screens (document Overview, approval) are where this costs real time.

Nothing here is a rewrite. The top five items are a day's work and would change how the app feels.


A. Hierarchy — where the time is lost

A1. The button wall on every document (HIGH)

Every document Overview renders four full-width stacked buttons — Sign · Request signatures · Affix e-Meterai · Affix stamp — regardless of whether any of them applies to that document, its status, or this user's role. Add the publish callout and the office banner and the document itself is two screens down.

The effect on a phone: the screen reads as a menu, not a document. The four slabs occupy ~45% of the first viewport.

Fix: one primary action + a "More actions…" sheet for the rest. Choose the primary from state (a draft you own → Request signatures; a document awaiting you → the duty; otherwise Sign). Ceremonies that can't apply shouldn't render at all.

A2. The duty screen offers the same action three times (HIGH)

On a document routed to you for signature, the screen shows:

  1. the generic blue Sign button,
  2. a duty callout with a blue Open the signing ceremony button,
  3. a sticky bottom bar with a blue Open the signing ceremony button.

Three blue buttons, two with identical labels, all leading to the same place. This is a regression from stacking the R13 sticky duty bar on top of the existing in-tab callout.

Fix: when a duty exists, it owns the screen — suppress the generic ceremony buttons and drop the callout's button (keep the callout's sentence, it's good). Sticky bar only.

A3. Secondary buttons outweigh the primary (HIGH, cheap)

Carbon's secondary button is a dark slab. On the light theme those four dark slabs are visually heavier than the blue primary above them, so the eye lands on the least important action. In dark mode the same button becomes mid-grey on near-black and reads as disabled.

Fix: use Carbon tertiary (outline) for supporting actions. Exactly one filled button per screen.

A4. The approver is given no context (HIGH)

The approval screen — the highest-value screen in the product — shows a section label, an unlabeled note box, a lone centred "Forward (disposisi)…" link, and three sticky buttons. It never says who requested the approval, which step of how many, when it's due, or what previous approvers wrote.

Deciding requires leaving the screen. That is backwards.

Fix: a requester + step + deadline summary directly under the header, prior notes inline, and label the note field ("Optional for approve, required to reject or return").

A5. Duplicate title, hidden tabs (MEDIUM)

The nav bar shows the (truncated) document title and the hero card repeats it in full one line below — ~120px of the first viewport spent twice. Separately, the six tabs are a horizontal ScrollView with showsHorizontalScrollIndicator={false} and no edge fade: measured, 78px of the strip sits off-screen, which is exactly the Activity tab — invisible and undiscoverable.

Fix: hero shows the title, nav bar shows the doc type or reference. Add a right-edge fade or fit the tab labels.


B. Touch targets & accessibility

B1. Chips are 33pt tall — below Apple's 44pt minimum (HIGH)

Measured in the running app: every Chip is 33pt tall (paddingVertical: 8 + 16px line-height + borders). For comparison, buttons measure 44pt, fields 45pt and action icons 59pt — the chip is the only control below the line. Chips carry classification, share protection, deadlines, signature tiers, document types, signer placement and notification frequency — this is systemic, not cosmetic. Thumbs miss, and mis-taps in the share protection row change the security posture of a link.

Fix: paddingVertical: 12 (→ 42pt) plus minHeight: 44. One-line change in components/ui.tsx.

B2. Body text is 14px, secondary labels 12px (MEDIUM)

iOS body default is 17pt. This app runs on a government/enterprise fleet that skews older. body01: 14 for all body copy and label01: 12 for chips, tab labels and action-icon captions is small enough to slow reading.

Fix: body01 → 16, label01 → 13, in packages/shared tokens. Also set maxFontSizeMultiplier on the fixed-height components before Dynamic Type breaks their layout — currently unset everywhere (0 usages).

B3. Icon-only controls are mostly unlabeled (MEDIUM)

12 accessibilityLabels across ~20 Pressables plus every ActionIcon and IconButton. VoiceOver announces most of them as "button". IconButton accepts a label but callers rarely pass one.

Fix: make accessibilityLabel required on IconButton/ActionIcon; TypeScript will find every site.

B4. Disabled primary fails contrast (MEDIUM)

Upload's disabled button is white text on light blue (~2:1). It reads as an enabled button that doesn't respond. Use a neutral disabled fill with textDisabled.


C. States & flows

C1. Only 6 of 17 screens handle a network error (HIGH)

The rest either spin forever or render an empty state that says "nothing here" when the truth is "we couldn't reach the server". The project's own success criteria say "zero 'what happened?' states".

Fix: one <QueryState> wrapper handling loading / error+retry / empty, applied at every list and detail root.

C2. Archive buries documents under folders (MEDIUM)

Folders render first, each in its own bordered card at ~65pt per row, so with a dozen folders you scroll past everything before a single document appears. No counts, no sort, no breadcrumb. The FAB overlaps the last row's chevron, and a floating action button is a Material pattern in an otherwise iOS-first app.

Fix: tighten rows to ~48pt, show folder item counts, move "+" into the nav bar.

C3. "Preview" vs "View" is unlearnable (MEDIUM)

Two adjacent icons labelled Preview and View. The real distinction — watermarked in-app render vs the traceable authoritative copy — is invisible in the words. Rename to Preview and Open original (traceable), or merge into one action that explains the trade-off on tap.

C4. Upload renders the type chips above their own label (LOW — real defect)

In DocTypePicker the chip row is emitted before the Field, so the visual order is [chips] → "Document type (optional)" → [input]. The chips are orphaned from the label they belong to. The equivalent editor in the Details tab has it right.

C5. Account screen polish (LOW)

Display name falls back to the email, printed directly above the same email. Each device gets a full-width red Revoke button — destructive weight for a routine action; on iOS this is a swipe action or a small destructive text button. "Active 7/27/2026, 4:18:31 AM" should be relative time.


What's already good (keep)

  • The ceremonies. Tier cards with plain-language trade-offs, the placement editor, the OTP screen, biometric confirm — this is the strongest part of the app and it earns the product's trust claims.
  • Dark mode is genuinely complete: proper layer tokens, no raw hex leaking, the deliberate white "paper" behind signature ink is the right call.
  • Honest egress language. Preview vs traceable copy, "every link can be revoked", the watermark notice bar — the app tells the truth about what leaves it.
  • One component kit. SectionLabel / Row / Chip / Callout / StatusTag are used consistently; that's why these fixes are small.
  • Empty states are written like a person wrote them ("Nothing needs you. 🎉").

Priority order

# Fix Impact Effort
1 Collapse the button wall (A1) + de-duplicate the duty CTA (A2) Highest — every document, every day ~3 h
2 Chip touch targets to 44pt (B1) Systemic mis-taps, incl. security controls ~15 min
3 Secondary → tertiary buttons (A3) Fixes hierarchy in both themes ~30 min
4 Approver context block (A4) The product's core decision moment ~2 h
5 QueryState error/retry wrapper (C1) Kills the "what happened?" class ~2 h

Everything in section B and C beyond this table is a half-day cleanup pass.