Skip to content

Launch Readiness Synopsis

Snapshot as of v1.13 (April 2026). See v1.14 milestone for cleanup and current component list.

A comprehensive record of all quality audit, WCAG 2.1 AA accessibility, and supporting sites changes shipped in the feature/launch-readiness branch.

Branch: feature/launch-readinessPR: #288Date: April 2026 Scope: 100+ files changed across quality, accessibility, performance, dashboard rethink, and Focus Mode


Table of Contents

  1. Overview
  2. Quality Audit — Feature 18
  3. Accessibility — WCAG 2.1 AA
  4. Supporting Sites Overhaul
  5. Infrastructure & Tooling
  6. Files Changed
  7. Metrics

Overview

This branch prepares Objectuve for public launch by addressing three workstreams:

graph LR
    A[Feature 18: Quality Audit] --> D[Launch Ready]
    B[WCAG 2.1 AA Accessibility] --> D
    C[Supporting Sites Overhaul] --> D

    A --> A1[9 views audited]
    A --> A2[20 quality checks each]

    B --> B1[Phase 3 baseline]
    B --> B2[Phase 4 full audit]

    C --> C1[Docs reorganized]
    C --> C2[Cross-site links]

Roadmap Updates

Features 10, 16, and 17 were marked as shipped with all acceptance criteria checked off:

FeatureNameCriteria Checked
10Push Notifications (FCM/APNs + Capacitor)8/8
16Screen Time Nudges7/7
17Quick-Add from Notification5/5

Quality Audit

Systematic audit of all 9 user-facing views against 20 quality checks.

Views Audited

graph TD
    subgraph "All 9 Views Audited"
        D[Dashboard - 848 lines]
        G[Goal Detail - 1339 lines]
        GL[Goals List - 536 lines]
        C[Communities - 807 lines]
        CD[Community Detail - 364 lines]
        A[Achievements - 420 lines]
        S[Settings/Profile - 354 lines]
        F[Activity Feed - 123 lines]
        P[Profile - in Settings]
    end

Error States Added

Every view now shows a helpful error message with a recovery action when API queries fail.

Before: Silent failures or blank screens when Apollo queries error.

After: Friendly error panels with retry buttons.

+------------------------------------------+
|                                          |
|        (!) Could not load goals          |
|                                          |
|    Something went wrong. Please check    |
|    your connection and try again.        |
|                                          |
|           [ Retry ]                      |
|                                          |
+------------------------------------------+

Views with new error states:

  • Dashboard.vue — Combined error from USER_QUERY and GOALS_QUERY
  • Goals.vue — Error from goals query with retry
  • Goal.vue — Error when goal can't be loaded (replaces silent redirect)
  • Communities.vue — Error from communities query
  • Community.vue — Error from community detail query + join failure toasts
  • Achievements.vue — Error from user/badges query

Loading States Improved

Before (Goals.vue): Full-viewport spinner causing large layout shift when content arrived.

After (Goals.vue): Dimension-matched skeleton layout: header skeleton + 4 stat cards + 6 LoadingSkeletonCard instances in the same grid layout as real content.

+--[Skeleton Header]-------------------------+
|  [====]  [===========]                     |
+--------------------------------------------+
| +--------+ +--------+ +--------+ +--------+|
| |  ====  | |  ====  | |  ====  | |  ====  ||
| | ====== | | ====== | | ====== | | ====== ||
| +--------+ +--------+ +--------+ +--------+|
+--------------------------------------------+
| +------------------+ +------------------+  |
| | [LoadingSkeleton] | | [LoadingSkeleton]|  |
| |   Card matches   | |   Card matches   |  |
| |   real layout    | |   real layout    |  |
| +------------------+ +------------------+  |
+--------------------------------------------+

Dashboard.vue: Replaced single-row skeleton with 4-column momentum bar + goal card grid matching real content dimensions.

Empty States with Actionable Guidance

Achievements.vue (new): When filters produce zero results, shows contextual empty state:

+------------------------------------------+
|                                          |
|    No badges match your filters          |
|                                          |
|    Try adjusting your category or        |
|    status filters to see more badges.    |
|                                          |
|        [ Clear Filters ]                 |
|                                          |
+------------------------------------------+

Dark Mode Fixes

Replaced hardcoded colors with design tokens across multiple components:

ComponentBeforeAfter
Goals stat cardsbg-white/80bg-card/80
Goals stat iconstext-blue-500text-primary
Communities searchbg-white dark:bg-slate-800bg-background
Communities sortbg-white dark:bg-slate-800bg-background
Communities filtersbg-white dark:bg-slate-800bg-background
UserFeedListbg-whitebg-white dark:bg-slate-900
Achievements badge counttext-slate-500text-muted-foreground
Goal.vue enhancement bannerHardcoded #fffbebcolor-mix() adaptive
Goal.vue timeline empty statergba(255,255,255,0.5)var(--ion-card-background)

Tap Feedback

Added active:scale-95 or active:opacity-70 to all interactive elements across all views. Every button, card, and clickable element now provides immediate visual feedback on press.

Duplicate-Tap Guards

Added loading guards to prevent double-submission on mutation buttons:

ViewMutationGuard
DashboardUPDATE_GOAL_MUTATIONupdatingGoal.value early return
Goal DetailHabit check-inhabitCheckInLoading + disabled button
Goal DetailComplete goalisCompletingGoal ref + finally block
Goal DetailToggle privacyisTogglingPrivacy ref + disabled buttons
CommunitiesJoin/leaveisJoining ref keyed by community ID
CommunitiesCreate communityisCreatingCommunity ref

Bug Fix

Dashboard.vue: Target icon from lucide-vue-next was used in template but never imported — a pre-existing runtime bug producing a Vue component resolution warning. Fixed by adding the import.


Accessibility

Infrastructure

graph TD
    subgraph "New A11y Infrastructure"
        E[eslint-plugin-vuejs-accessibility]
        CA[cypress-axe]
        JA[jest-axe]
        UC[useAccessibility.ts composable]
        SL[Skip-to-main link]
        AR[Aria-live announcer]
        RM[prefers-reduced-motion CSS]
        DOC[accessibility.md docs]
        CT[Cypress a11y smoke test]
        VT[Vitest a11y helper]
    end

    E --> |Lint-time| DEV[Developer Workflow]
    CA --> |E2E| CI[CI Pipeline]
    JA --> |Unit| CI
    UC --> |Runtime| APP[Application]
    SL --> APP
    AR --> APP
    RM --> APP

useAccessibility.ts Composable

Four reusable utilities:

typescript
// Focus trap for modals/dialogs
const { activate, deactivate } = useFocusTrap(containerRef)

// Screen reader announcements
const { announce } = useAnnounce()
announce('Badge unlocked: First Steps')

// Reactive reduced-motion preference
const { reducedMotion } = useReducedMotion()

// Programmatic skip to main content
const { skipToMain } = useSkipLink()
+--[Visible only on Tab focus]--------+
| Skip to main content                |
+-------------------------------------+

Added to App.vue. Visually hidden by default, becomes visible when focused via keyboard Tab. Links to #main-content (the ion-router-outlet).

Global prefers-reduced-motion

css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Plus PageTransition.vue skips enter/leave animations when reduced motion is preferred.

Minimum Tap Targets (44px)

WCAG 2.1 AA requires 44x44px minimum touch targets.

ComponentBeforeAfter
UiButton defaulth-10 (40px)h-11 (44px)
UiButton smh-9 (36px)h-10 (40px)
UiButton lgh-11 (44px)h-12 (48px)
UiButton iconh-10 w-10 (40px)h-11 w-11 (44px)
UiInputh-10 (40px)h-11 (44px)
ActivityFeed refreshNo min sizemin-w-[44px] min-h-[44px]
ActivityFeed tabsDefaultmin-h-[44px]

ARIA Tab Patterns

Added full tab widget semantics to 4 views:

html
<!-- Before -->
<div class="tabs">
  <button @click="activeTab = 'profile'">Profile</button>
  <button @click="activeTab = 'security'">Security</button>
</div>
<div v-show="activeTab === 'profile'">...</div>

<!-- After -->
<div role="tablist" aria-label="Settings sections">
  <button role="tab" :aria-selected="activeTab === 'profile'"
          :aria-controls="'panel-profile'">Profile</button>
  <button role="tab" :aria-selected="activeTab === 'security'"
          :aria-controls="'panel-security'">Security</button>
</div>
<div id="panel-profile" role="tabpanel" aria-labelledby="tab-profile"
     v-show="activeTab === 'profile'">...</div>

Applied to: Settings, ActivityFeed, Goals, Community Detail

+--[role="banner"]--[AppHeader]--------+
|  [menu-button]  [logo]              |
+--------------------------------------+
|                                      |
| [role="navigation" aria-label=      |
|  "Main menu"] -- SideMenu            |
|                                      |
| [#main-content]                      |
| [aria-live="polite"]                 |
|   ... page content ...               |
|                                      |
+--------------------------------------+
| [role="navigation" aria-label=      |
|  "Main navigation"] -- BottomTabBar  |
| [aria-current="page" on active tab] |
+--------------------------------------+

All 12 modals received:

  • aria-labelledby pointing to modal title
  • aria-label on close/dismiss buttons
  • aria-hidden="true" on decorative icons
  • Focus traps on 5 custom div-based modals (via useFocusTrap)
ModalTypeFocus Trap
AiCoachModalIonModalNative
AgreeToTermsModalIonModalNative
BadgeModalCustom divuseFocusTrap
ChangelogModalIonModalNative
CommunitySuggestionModalIonModalNative
CommunityComparisonModalCustom divuseFocusTrap
CommunityPreviewModalCustom divuseFocusTrap
CreateCommunityModalCustom divuseFocusTrap
MemberProfileModalCustom divuseFocusTrap
ProfileCompletionModalIonModalNative
ReportContentModalIonModalNative
StreakDetailsModalIonModalNative

Form Accessibility

All form fields now have properly associated labels:

html
<!-- Before -->
<label>First Name</label>
<UiInput v-model="form.firstName" />

<!-- After -->
<label for="settings-firstName">First Name</label>
<UiInput id="settings-firstName" v-model="form.firstName" />

Files with form label fixes: Settings.vue (8 fields), GoalForm.vue (6 fields), GoalCreate.vue (6 fields), GoalEventForm.vue (3 fields), CreateCommunityModal.vue (6 fields), ProfileCompletionModal.vue (3 fields), MoodCheckIn.vue (2 fields), CommunityComparisonModal.vue (1 field), AiCoachModal.vue (1 field), SignIn.vue (2 fields), OnboardingWizard.vue (1 field), AchievementFilters.vue (1 field), CommunityFeed.vue (2 fields), CommunityGoals.vue (1 field), CommunityFeedCard.vue (1 field)

Radiogroup Patterns

MoodSelector: Full ARIA radiogroup with roving tabindex and keyboard navigation (arrow keys).

+--[role="radiogroup" aria-labelledby="mood-group-label"]--+
|                                                           |
|  [role="radio" aria-checked="false" tabindex="-1"]       |
|  Terrible                                                 |
|                                                           |
|  [role="radio" aria-checked="true" tabindex="0"]         |
|  Amazing  <-- focused, selected                          |
|                                                           |
|  [role="radio" aria-checked="false" tabindex="-1"]       |
|  Great                                                    |
|                                                           |
+----------------------------------------------------------+
  Arrow keys move selection, Tab moves out of group

GoalForm type picker: role="radiogroup" with aria-checked on each goal type option.

Keyboard Support

Converted all clickable <div> elements to either:

  1. Semantic <button> elements (preferred), or
  2. role="button" + tabindex="0" + @keyup.enter handler

Components converted to <button>:

  • GoalCard image container
  • GoalEventCard content + action areas
  • GoalListItem card
  • AchievementCard
  • ShowcaseSelector badge options
  • AllyActivityFeed rows
  • MilestoneManager toggle + name display

Components with role="button" added:

  • SideMenu: logo, profile, nav items, CTA, version info
  • AppHeader logo
  • CommunityCard, GoalCard (ui/), NotificationsPanel items
  • Dashboard encouraged-goal rows (converted to <button>)
  • GettingStartedCard collapsed state
  • CommunityMembers grid + leaderboard rows
  • CommunityGoals cards
  • GoalGridCard, ShowcasedAchievements, TodaysHabits
  • CommunityListItem, EnhancedCommunityCard, CommunityInsightsPanel
  • OnboardingTour backdrop, SmartNotifications cards

Dynamic Content (aria-live)

ComponentAttributePurpose
ActionAlertrole="alert" aria-live="assertive"Goal needs action
ScreenTimeNudgerole="alert" aria-live="polite"Break reminder
Error states (all views)role="alert" aria-live="assertive"Query failures
UserFeedListaria-live="polite"New feed items
Dashboard contentaria-live="polite"State changes
Goals tab panelsaria-live="polite"Filter/sort changes
Community tab panelsaria-live="polite"Tab content changes
Achievements badge countaria-live="polite"Filter count updates
Goal.vue timelinearia-live="polite"New events after check-in
Goal.vue encouragementsaria-live="polite"Count changes
Dashboard notificationsaria-live="polite"Unread count badge
Loading containersaria-busy="true"While fetching
LoadingAnimationrole="status"Spinner announcement
LoadingSkeletonCardrole="status" aria-busy="true"Skeleton announcement
GoalDetailSkeletonrole="status" aria-busy="true"Goal detail page skeleton announcement
AchievementsSkeletonrole="status" aria-busy="true"Achievement Hall page skeleton announcement
PublicGoalSkeletonrole="status" aria-busy="true"Public goal page skeleton announcement
PublicProfileSkeletonrole="status" aria-busy="true"Public profile page skeleton announcement

Image Alt Text

All <img> tags audited across 16+ files:

PatternAlt Text
User avatars:alt="'Profile photo for ' + userName"
Badge images:alt="badge.name + ' badge'"
Check-in photosalt="Check-in photo"
Community covers:alt="community.name"
Goal cover imagesalt="Goal cover image"
Decorative hero imagesalt=""

Decorative Icons

All decorative <ion-icon> and Lucide icon components marked with aria-hidden="true" across 60+ files — prevents screen readers from announcing purely visual elements like navigation arrows, status indicators, and ornamental icons.


Dashboard Rethink (11 → 6 sections)

The active user dashboard was restructured from 11 sections to 6:

Before (11 sections)After (6 sections)
MomentumBarMomentumBar (now shown for ALL users, not just 3+)
Daily QuoteMoved to greeting subtitle
ActionHubMerged into TodayActions
CheckInPromptCardMerged into TodayActions
TodaysHabitsMerged into TodayActions
AiInsightCardMerged into TodayActions (single AI prompt)
Your Goals (5 cap, with check-in)Your Goals (all goals, view-only)
Community SupportMerged into SocialSection
Ally Activity FeedMerged into SocialSection
WeeklyActivity (with stats grid)WeeklyActivity (day circles only)
Showcased AchievementsShowcased Achievements (unchanged)

New components: TodayActions.vue, SocialSection.vueSimplified: WeeklyActivity.vue (removed duplicate stats grid), GoalGridCard.vue (removed check-in button)

Focus Mode (Streamlined Dashboard)

Card-stack swipe interface for quick daily check-ins (~2-3 minutes):

CardContentAction
GreetingStreak + quoteSwipe
Mood6 mood optionsTap mood
Habits (1 per)Name + streak + ringTap check-in
Urgent GoalName + days sinceAdd Progress / Dismiss
AI InsightCoach messageTap to open coach
Social NudgeEncouragementNice!
DoneConfetti + statsSee Full Dashboard

Three modes: Focus (always cards), Full (always dashboard), Auto (smart switch) Settings toggle: 3-button selector matching the theme toggle pattern Gesture: Ionic createGesture for horizontal swipe, CSS transitions New files: useFocusMode.ts, StreamlinedDashboard.vue, 7 card components


Supporting Sites

Docs Landing Page

Added "Supporting Sites" table to docs/index.md:

SiteURLDescription
Component Librarystorybook.objectuve.comStorybook with 30+ stories
Design Systemdesign.objectuve.comColors, typography, spacing
API Explorerenkidu-api-docs.web.appGraphQL Voyager
Marketing Sitewww.objectuve.comLanding page & policies
Production Appapp.objectuve.comLive app
Staging Appstaging.app.objectuve.comPre-release testing

VitePress Sidebar Reorganization

Before: Flat list with missing pages.

After: Organized into subsections with 15+ new entries:

Feature Guides
  Core: Goals, Habits, Dashboard, Mood Logging, Progress Viz
  Engagement: Achievements, Gamification Engine, Coach,
              Check-in Prompts, Notifications, Push Notifications
  Social: Communities, Social Features, Activity Feed,
          Allies, User Profile
  Admin: Admin Dashboard, Onboarding Timeline

Reference
  + Accessibility (NEW)
  + Color Philosophy
  + Logo Evolution & Transition
  + Legal & Policies

Operations
  + Observability
  + Alerting
  + Dashboards
  + Vendor Accounts

Infrastructure

ESLint A11y Plugin

eslint-plugin-vuejs-accessibility (recommended config)

Custom rules:
  label-has-for: allows nesting or id, recognizes UiInput/UiSelect/UiTextarea
  form-control-has-label: recognizes custom + Ionic form components

Cypress A11y Smoke Test

New file: tests/e2e/specs/accessibility/a11y-smoke.cy.ts

Tests all 6 core views against WCAG 2.1 AA using cypress-axe:

  • Dashboard, Goals, Communities, Achievements, Settings, Feed
  • Verifies skip-to-main link exists and is focusable
  • Verifies aria-live announcer region exists

Vitest A11y Helper

New file: tests/unit/helpers/a11y.ts

typescript
import { expectNoA11yViolations } from '@tests/unit/helpers/a11y'

it('has no a11y violations', async () => {
  const wrapper = mount(MyComponent)
  await expectNoA11yViolations(wrapper)
})

New Dependencies

PackageVersionPurpose
eslint-plugin-vuejs-accessibilitylatestLint-time a11y checks
cypress-axelatestE2E a11y testing
axe-corelatestA11y engine (shared)
jest-axelatestVitest a11y assertions
@types/jest-axelatestTypeScript types

Files Changed

New Files (4)

FilePurpose
src/composables/useAccessibility.tsFocus trap, announcer, reduced motion, skip link
tests/e2e/specs/accessibility/a11y-smoke.cy.tsCypress a11y smoke test
tests/unit/helpers/a11y.tsVitest a11y helper
docs/development/accessibility.mdA11y standards document

Modified Files by Category

Views (11): Dashboard, Goal, Goals, GoalCreate, Communities, Community, Achievements, ActivityFeed, Settings, SignIn, PublicGoal

Core Components (16): ActionAlert, BottomTabBar, GoalCard, GoalEventCard, GoalEventForm, GoalEventItem, GoalForm, GoalListItem, LoadingAnimation, LoadingSkeletonCard, MilestoneManager, MoodCheckIn, MoodSelector, OnboardingWizard, PageTransition, ReactionPicker, SideMenu, UserFeedList

Modal Components (12): AiCoachModal, AgreeToTermsModal, BadgeModal, ChangelogModal, CommunitySuggestionModal, CommunityComparisonModal, CommunityPreviewModal, CreateCommunityModal, MemberProfileModal, ProfileCompletionModal, ReportContentModal, StreakDetailsModal

UI Primitives (6): UiButton, UiInput, UiTooltip, AppHeader, CommunityCard, GoalCard (ui/), NotificationsPanel, ScreenTimeNudge

Community Components (8): AllyActivityFeed, CommunityInsightsPanel, CommunityListItem, EnhancedCommunityCard, OnboardingTour, SmartNotifications, CommunityFeed, CommunityFeedCard, CommunityGoals, CommunityMembers

Dashboard Components (5): GettingStartedCard, GoalGridCard, ShowcasedAchievements, StreakDetailsModal, TodaysHabits

Other (3): AdminUsersTable, TimelineEvent, PublicTimelineEvent

Docs & Config (5): roadmap.md, roadmap-visual.md, index.md, accessibility.md, .vitepress/config.ts, eslint.config.js, package.json


Metrics

MetricBeforeAfterChange
ESLint a11y errors~1500-100%
ESLint warnings72-71%
Views with error states06+6
Views with matched skeletons02++2
Components with aria-label760++750%
Modals with aria-labelledby012+12
Modals with focus traps05+5
Images with proper alt text~216++700%
Forms with label associations~540++700%
Interactive divs with keyboard~530++500%
Minimum tap target (UiButton)40px44pxWCAG AA
prefers-reduced-motion supportnoneglobalfull
TypeScriptpassingpassingmaintained
Vitest (444 tests)passingpassingmaintained
Docs sidebar entries~3045++50%

Last updated: 2026-07-09

Loading…