Skip to content

Commit de0573c

Browse files
bryanfawcettclaude
andauthored
refactor(profile,ui): flat grouped profile edit + component library dedup (#116)
* refactor(profile): flatten edit-profile into one grouped page, no tabs Replaced the Profile/Preferences tab shell (NyuchiProfileSettings) with a single continuously-scrollable page -- every section (Avatar, Name, Personal details, Location, Interests, Notifications, Language, Appearance) is always visible and directly editable, grouped into clearly labelled cards. Matches the flat, grouped-and-labelled settings pattern the rest of the ecosystem uses, instead of hiding half the form behind a tab switch. * refactor(ui): consolidate avatar-initials logic onto one shared util Nine components each reimplemented "initials from a name" slightly differently -- some skipped .trim()/.toUpperCase(), split on a literal space instead of /\s+/, or ordered slice/uppercase differently, so the same name could render different initials depending which component showed it. Added src/lib/avatar-initials.ts (getInitials, with tests) as the one shared implementation and pointed every call site at it, including mappers.ts's existing initialsFromName (now a thin delegate, keeping its stable name/signature for its own callers). * refactor(ui): NyuchiReviewCard uses the shared Rating component Replaced its own inline Array.from({length:5}).map(...) star loop (the only place in the app duplicating what src/components/ui/ rating.tsx already does) with <Rating readOnly size="sm" />. * refactor(ui): trim useNyuchiHarness to the fields components actually use Audited all 27 harness consumers: theme/locale/reportHealth/ announceUrgent/prefersReducedMotion were computed and returned on every hook call but never read by a single one. theme was the expensive one -- a MutationObserver on document.documentElement stood up per mount across all 27 components purely to produce a value nobody consumed. announceUrgent was also just announce under another name; there is no assertive live region anywhere to justify a separate "urgent" path. useNyuchiHarness now returns { log, motion, animStyle, announce } -- the four fields real call sites destructure. Left NyuchiHarness (the declarative wrapper) alone: it has its own passing tests and is documented as one of the harness's two entry points, so removing it outright is a bigger call than trimming a hook's dead return fields. * feat(profile): split preferences into its own page using NyuchiProfileSettings Adopts NyuchiProfileSettings for real -- the one nyuchi-* component with zero call sites anywhere in the app. Its shape (section sidebar + active content + sticky save bar) doesn't fit /profile/edit's flat identity form, but it's exactly right for app-behavior preferences (notifications, language, appearance), which now live at their own /profile/preferences route with three real, switchable sections. /profile/edit keeps only identity fields (avatar, name, personal details, location, interests) and links out to the new page. The /profile hub's menu splits into distinct "Profile" and "Preferences" sections pointing at the right destination. * refactor(profile): promote AvatarPicker to a harness-wired nyuchi component Converts the plain AvatarPicker into NyuchiAvatarPicker: wired through useNyuchiHarness for motion/announce, matching every other branded component in the library. /profile/edit is the only consumer. No canonical avatar-picker exists in the Mzizi registry (confirmed via search) — filed as a new-component proposal via component feedback (issue #28) so the ecosystem gets one shared implementation instead of each app rebuilding the upload/Gravatar/sticker picker independently. * refactor(profile): consolidate edit + preferences into one sidebar-tab settings page Undoes the earlier two-page split (/profile/edit + /profile/preferences). Per feedback, everything belongs in one "profile settings" page with a sidebar tab experience (Profile / Location / Interests / Notifications / Language / Appearance) — the pattern most settings surfaces (Claude, Slack, etc.) use — rather than separate pages or components. /profile/edit now renders NyuchiProfileSettings with all six sections and a single save action that diffs every field (identity + notifications + locale) into one updateMyProfile call. The profile hub's Language/Event update rows deep-link via `?section=`, read once on mount (matches the existing /search `?q=` pattern — no useSearchParams Suspense boundary). /profile/preferences is removed. * fix(mobile): resolve two mobile-first defects in the new settings page The global floating MobileBottomNav (a fixed pill bar) already had an exclusion list for pages with their own sticky bottom action bar (/events/create, /signage, /kiosk, /manage) — /profile/edit was missing from it, so on mobile the floating nav sat on top of the avatar sticker grid and collided with the settings page's own Save/Cancel bar. Also: NyuchiProfileSettings' section tabs live in a horizontally- scrolling row on mobile. Deep-linking into a section past the first screenful (e.g. /profile/edit?section=language, used by the profile hub's Language/Notifications rows) rendered the right content with no matching tab visible or highlighted. The active tab now scrolls itself into view on mount/change, honoring reduced-motion. * style(mobile-nav): dock the bottom bar to the screen edge, drop the floating pill The primary mobile nav (Home/Discover/Calendar/My Events/Profile) was a floating rounded pill with side margins and a gap above the safe area. Restyled to a standard docked, full-width bottom tab bar flush to the edge — matching how Instagram/TikTok/X render their own primary nav — instead of introducing a second, competing "floating vs. dashboard" nav idiom. Same items, same active-state logic, same hidden-path rules. --------- Co-authored-by: Bryan Fawcett <noreply@anthropic.com>
1 parent 680fac5 commit de0573c

21 files changed

Lines changed: 355 additions & 412 deletions

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ shadcn/Radix primitives installed from the Mzizi design-system registry (`mzizi.
190190
The infrastructure spine the mzizi-branded component library compiles against. It **unifies Nhimbe's existing infra modules** behind one contract rather than re-implementing them — observability (`src/lib/observability.ts`), a11y announcements (`src/components/ui/live-region.tsx`), error resilience (`src/components/error/section-error-boundary.tsx`), and skeleton loading (`src/components/ui/skeleton.tsx`). Motion is token-driven (`--motion-duration-*` / `--motion-ease-*`, with fallbacks) and honours `prefers-reduced-motion`; shared entry keyframes are injected at runtime so `globals.css` stays owned by the design-system PR. Two entry points:
191191

192192
- **`NyuchiHarness`** — declarative section wrapper: `<NyuchiHarness name="feed" loading skeleton={…} fallback={…}>…</NyuchiHarness>` (error boundary + skeleton + render-timing log + entry animation + a11y roles).
193-
- **`useNyuchiHarness(name)`** — imperative hook for leaf brand components, returning `{ log, motion, animStyle, prefersReducedMotion, locale, theme, reportHealth, announce, announceUrgent }`. Also exports the standalone `animStyle()` / `prefersReducedMotion()` helpers.
193+
- **`useNyuchiHarness(name)`** — imperative hook for leaf brand components, returning `{ log, motion, animStyle, announce }` (trimmed to the fields components actually consume — a `theme`/`locale`/`reportHealth`/`announceUrgent` surface was built but never read, including a per-mount `MutationObserver` behind `theme` across all 27 harness consumers). Also exports the standalone `animStyle()` / `prefersReducedMotion()` helpers.
194194

195195
`verified-badge.tsx` (trust-tier verification badge; tanzanite top tier) is the first brand component wired to it. New brand components should consume the harness rather than touching the underlying modules directly.
196196

@@ -211,7 +211,7 @@ Per-event mineral accents come from **`src/lib/category-mineral.ts`** (`category
211211

212212
#### nyuchi identity / community / trust components (ported from mzizi)
213213

214-
Beyond the event-domain cards, the wider mzizi brand library now lives in `src/components/ui/` (all `nyuchi-*`, each colocated with a `.test.tsx` and wired through the harness). Identity & profile: `nyuchi-profile-header`, `nyuchi-profile-block`, `nyuchi-profile-settings`, `nyuchi-user-card`, `nyuchi-user-menu`, `nyuchi-avatar-stack`, `nyuchi-onboarding-step`. Community & content: `nyuchi-group-card`, `nyuchi-article-card`, `nyuchi-review-card`, `nyuchi-content-composer`, `nyuchi-search-view` (`NyuchiSearchView`, the `/search` results view), `nyuchi-command-palette` (`NyuchiCommandPalette` — the ⌘K global palette wired into the header: grouped "Go to" nav + live event results, mineral chips, keyboard nav, `/search?q=` fallback), `nyuchi-sidebar-nav`, `nyuchi-notification-item`, `nyuchi-action-sheet`, `nyuchi-alert-banner`, `nyuchi-feedback`, `nyuchi-empty-state`, `nyuchi-success-screen`. Trust & verification: `verified-badge` (the mineral-tier badge), `nyuchi-trust-meter`, `nyuchi-source-badge`, `nyuchi-badge-display`, `nyuchi-leaderboard-row`. Cover & stats: `nyuchi-cover-header`, `nyuchi-cover-wash-header`, `nyuchi-hero-stat`, `nyuchi-stats-row`, `nyuchi-share-card`. Commerce/place: `nyuchi-offer-card`, `nyuchi-place-card`, `nyuchi-registration-card`. Not every component is wired into a live surface yet — the library is adopted incrementally, page by page.
214+
Beyond the event-domain cards, the wider mzizi brand library now lives in `src/components/ui/` (all `nyuchi-*`, each colocated with a `.test.tsx` and wired through the harness). Identity & profile: `nyuchi-profile-header`, `nyuchi-profile-block`, `nyuchi-profile-settings`, `nyuchi-user-card`, `nyuchi-user-menu`, `nyuchi-avatar-stack`, `nyuchi-avatar-picker` (`NyuchiAvatarPicker` — upload/Gravatar/sticker three-way avatar selector, wired into `/profile/edit`; net-new, not yet in the Mzizi registry — proposed upstream as a candidate component, see the feedback log), `nyuchi-onboarding-step`. Community & content: `nyuchi-group-card`, `nyuchi-article-card`, `nyuchi-review-card`, `nyuchi-content-composer`, `nyuchi-search-view` (`NyuchiSearchView`, the `/search` results view), `nyuchi-command-palette` (`NyuchiCommandPalette` — the ⌘K global palette wired into the header: grouped "Go to" nav + live event results, mineral chips, keyboard nav, `/search?q=` fallback), `nyuchi-sidebar-nav`, `nyuchi-notification-item`, `nyuchi-action-sheet`, `nyuchi-alert-banner`, `nyuchi-feedback`, `nyuchi-empty-state`, `nyuchi-success-screen`. Trust & verification: `verified-badge` (the mineral-tier badge), `nyuchi-trust-meter`, `nyuchi-source-badge`, `nyuchi-badge-display`, `nyuchi-leaderboard-row`. Cover & stats: `nyuchi-cover-header`, `nyuchi-cover-wash-header`, `nyuchi-hero-stat`, `nyuchi-stats-row`, `nyuchi-share-card`. Commerce/place: `nyuchi-offer-card`, `nyuchi-place-card`, `nyuchi-registration-card`. Not every component is wired into a live surface yet — the library is adopted incrementally, page by page.
215215

216216
#### Venue verification (Kweli) — `src/lib/kweli.ts`
217217

0 commit comments

Comments
 (0)