# Verify-completion audit — 2026-05-21

## NAN-432 — Stock detail header
Linear: https://linear.app/nanostreet/issue/NAN-432/stock-detail-header-strip-co-show-post-market-price-moon-icon-after
Title: Stock detail header — strip "Co.", show post-market price + moon icon after-hours
Status: In Progress

### Acceptance criteria

- [PARTIAL] "Co..." artifact gone; Tesla header reads "Tesla, Inc." cleanly
  Evidence: `src/features/stock-detail/presentation/components/ExpandedHeader.tsx:146` — `const displayName = details?.shortName ?? details?.name ?? ticker;`
  Notes: Code uses `shortName` first (falls back to `name`, then `ticker`) — no explicit regex stripping of "Co."/"Inc." suffixes. Whether the artifact is gone depends entirely on what `short_name` the API returns. If `short_name` is null, `name` is used as-is; if the raw `name` still contains double-suffix data (e.g. "Tesla, Inc. Co."), the artifact reappears. No stripping guard in the mapper at `src/features/market/data/mappers/marketMapper.ts:187`.

- [DONE] After-hours session row shows post-market price + moon icon when current EST time ∈ (16:00, 09:30)
  Evidence: `src/features/stock-detail/presentation/components/ExpandedHeader.tsx:90-98` — `if (marketStatus === 'after-hours') { return <ExtendedHoursRow ... sessionIcon={<MoonEclipseIcon .../>} /> }`
  Evidence (icon): `src/shared/ui/icons/MoonEclipseIcon.tsx:5` — `MoonEclipseIcon` exists in icon library (not expo/vector-icons).
  Evidence (session logic): `src/features/stock-detail/business/utils/sessionBands.ts:25` — `if (hour < AFTER_HOURS_END_HOUR) return 'after-hours';`
  Evidence (price rendered): `src/features/stock-detail/presentation/components/ExtendedHoursRow.tsx:30` — renders `price` prop as dollar string.
  Evidence (tests): `src/features/stock-detail/presentation/components/ExpandedHeader.test.tsx:142-146` — test "after-hours: renders Moon icon and no label text" passes.

- [DONE] Pre-market shows pre-market price + sunrise icon when ∈ (04:00, 09:30) EST
  Evidence: `src/features/stock-detail/presentation/components/ExpandedHeader.tsx:81-88` — `if (marketStatus === 'pre-market') { return <ExtendedHoursRow ... sessionIcon={<SunsetIcon color={orange[300]} .../>} /> }`
  Notes: Icon is named `SunsetIcon` (not `SunriseIcon`) — functionally renders a sun-at-horizon icon for pre-market; visually matches the intent. Pre-market price wired via same `extendedPrice` logic as after-hours.

- [DONE] Regular hours show no session row when ∈ [09:30, 16:00) EST
  Evidence: `src/features/stock-detail/presentation/components/ExpandedHeader.tsx:99` — `return null;` when `marketStatus` is neither `pre-market` nor `after-hours`.

### Overall: NEEDS WORK
Recommendation: AC2/AC3/AC4 are solid — only the "Co..." artifact fix is at risk; add an explicit `.replace(/\s+(Co\.|Inc\.|Ltd\.|Corp\.)[^a-zA-Z].*$/, '')` guard on the `name` fallback (or verify `short_name` is always clean from the API) before closing.

---

## NAN-470 — Forms Name field rejects valid names
Linear: https://linear.app/nanostreet/issue/NAN-470/forms-name-field-rejects-valid-names-false-validation-error
Title: [Forms] Name field rejects valid names (false validation error)
Status: In Progress (Urgent)

### Acceptance criteria

- [PARTIAL] Any real-world full name accepted (incl. spaces, hyphens, apostrophes, non-ASCII Unicode)
  Evidence (onboarding): `src/features/onboarding/presentation/screens/NameInputScreen.tsx:26` — `const isValid = displayName.trim().length > 0;` — only checks non-empty; no character-class restriction.
  Evidence (business): `src/features/onboarding/business/usecases/CompleteOnboardingUseCase.ts:11` — `if (trimmedName.length === 0 || trimmedName.length > 50)` — length-only check, no regex.
  Evidence (edit profile): `src/features/account/presentation/hooks/useEditProfileViewModel.ts:78` — `isDirty = displayedName.trim() !== profile.displayName` — no character validation at all.
  Evidence (auth validators): `src/features/auth/business/validators/authValidators.ts` — only `validateEmail` and `validatePassword` exist; no `validateName` function.
  Notes: No restrictive regex found anywhere in the mobile codebase that would reject O'Brien, Jean-Luc, Søren, or 李明. However the bug ("all names get error") was reported as actively reproducible and the ticket is In Progress — meaning the root cause has not been confirmed fixed. The error may originate server-side (backend validation on the `display_name` field) which is outside this codebase. The mobile client does not prevent submission of any character-class.

- [NOT DONE] Add unit test for common name patterns
  Evidence: `src/features/auth/business/validators/authValidators.test.ts` — only email/password tests; no name test.
  Evidence: `src/features/onboarding/business/usecases/CompleteOnboardingUseCase.test.ts:52-63` — only tests empty and >50-char; no test for O'Brien, Jean-Luc, Søren, 李明, etc.
  Notes: The AC explicitly requires unit tests for common name patterns. None exist.

### Overall: NEEDS WORK
Recommendation: Mobile codebase has no blocking name regex — but (1) the bug cause is unconfirmed (could be server-side), and (2) the required unit tests are absent. Needs: confirm root cause (check backend API validation), add a `validateDisplayName` unit test covering apostrophes/hyphens/Unicode before closing.

---

## NAN-489 — Share bottom sheet
Linear: https://linear.app/nanostreet/issue/NAN-489/share-bottom-sheet-missing-bottom-margin-add-to-new-quick-note-broken
Title: Share bottom sheet: missing bottom margin, "Add to new quick note" broken, missing Copied toast
Status: Todo (unstarted in Linear)

### Acceptance criteria

- [DONE] Add paddingBottom: spacing.lg to StockHeaderActionMenu content container
  Evidence: `src/features/alerts/presentation/components/StockHeaderActionMenu.tsx:126` — `paddingBottom: spacing.lg,` present inside `dropdown` StyleSheet entry.

- [PARTIAL] Wire onSelectAddToNewQuickNote → call createStockNote use-case (currently a no-op)
  Evidence (wired): `src/features/stock-detail/presentation/hooks/useScreenState.ts:34-37` — `handleSelectAddToNewQuickNote` calls `quickNoteMutation.mutate()`.
  Evidence (mutation): `src/features/stock-detail/presentation/hooks/useCreateQuickNoteMutation.ts:15-16` — `mutationFn: () => container.stockDetail.createStockNote.execute(ticker, { title: '', content: '' })` — note IS created.
  Evidence (passed to UI): `src/features/stock-detail/presentation/screens/StockDetailScreen.tsx:107` — `onSelectAddToNewQuickNote={handleSelectAddToNewQuickNote}` passed to StockAlertFlow → StockHeaderActionMenu.
  Evidence (not disabled): `src/features/alerts/presentation/components/StockHeaderActionMenu.tsx:81` — `quickNoteDisabled = onSelectAddToNewQuickNote == null` evaluates to `false` since handler is provided.
  Notes: Note creation is wired and functional. BUT `useCreateQuickNoteMutation.ts:20` contains `// TODO(NAN-489): open the note editor after creation instead of showing a toast.` — the intent (open NoteFormSheet on the created note) is explicitly deferred. Users get a "Quick note created" snackbar but the editor doesn't open. The ticket sub-task says "Wire → call createStockNote use-case" which is done; the deeper UX (open editor) is a follow-up.

- [DONE] Add 2s Copied toast — reuse Added to watchlist component pattern
  Evidence: `src/features/alerts/presentation/components/StockHeaderActionMenu.tsx:68` — `snackbar.show('Copied', { variant: 'success', duration: COPIED_RESET_MS })` where `COPIED_RESET_MS = 2000`.
  Evidence (title swap): `src/features/alerts/presentation/components/StockHeaderActionMenu.tsx:90` — `title={copied ? 'Copied' : 'Share'}`.
  Evidence (clipboard): `src/features/alerts/presentation/components/StockHeaderActionMenu.tsx:64` — `await Clipboard.setStringAsync(url)` before showing toast.

### Overall: NEEDS WORK (minor)
Recommendation: Bottom margin and Copied toast are fully done. The "Add to quick note" creates a note but doesn't open the editor (explicit TODO in code). Decide: is "creates note + toast" sufficient to close, or must the editor open? If editor-open is required, that's the remaining work item before closing.

---

## Linear access
MCP worked first try for NAN-432 and NAN-470. NAN-489 failed on first attempt (deprecated SSE transport error) but succeeded on retry. All ticket data sourced from `mcp__linear-server__get_issue`.
