Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
40ecd60
feat(chat): refactor chat components into primitives and enhance UI
PostboxRetinal Aug 9, 2026
ee8e6d9
feat(ux): added model selector
PostboxRetinal Aug 9, 2026
5c24254
feat(ui): normalize UI components with updated styles and improved us…
PostboxRetinal Aug 9, 2026
caacee8
feat(ui): enhance ConversationView and ModelSelector with improved mo…
PostboxRetinal Aug 9, 2026
5b58cb1
feat(auth): retheme authentication screens to green-accent design and…
PostboxRetinal Aug 9, 2026
053fc21
feat(ui): implement sidebar collapse functionality and update UI styles
PostboxRetinal Aug 9, 2026
fb583cf
feat(ui): enhance ConversationView with voice transcription features …
PostboxRetinal Aug 9, 2026
59de6e0
feat(auth): rework login and signup screens to unify with green-accen…
PostboxRetinal Aug 9, 2026
33044e8
feat: unify composer bar and improve STT presentation
PostboxRetinal Aug 9, 2026
317c693
feat(ui): add editable conversation title with click-to-edit function…
PostboxRetinal Aug 9, 2026
81b299d
feat: update dependencies and improve configuration files for better …
PostboxRetinal Aug 9, 2026
9df2848
fix(bug): rollback for broken tailwind v4 upgrade
PostboxRetinal Aug 9, 2026
c6994ca
feat(ui): add pencil icon for editable conversation title
PostboxRetinal Aug 9, 2026
81d1fa4
fix(ui): remove unnecessary border from copy button in chat message c…
PostboxRetinal Aug 9, 2026
abf3fae
feat(ui): enhance chat composer with pencil icon, flat copy button de…
PostboxRetinal Aug 9, 2026
f0bc778
fix(ci/cd): removed extra char which made ci/cd WF fail
PostboxRetinal Aug 9, 2026
bbb86f8
fix(ci/cd): added missing eslint not included in newly migrated deps
PostboxRetinal Aug 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ tsconfig.tsbuildinfo
node_modules
dist
dist-ssr
coverage
*.local
.pnpm-store

Expand Down
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# DevVoice

A voice-powered developer research assistant built for the native.builder hackathon. Speak a question, hear it transcribed, and get a researched answer with clickable source citations: all in a dark, AMOLED-first interface.
A voice-powered developer research assistant built for the native.builder hackathon. Speak a question, hear it transcribed, and get a researched answer with clickable source citations all in a dark, AMOLED-first interface.

## Highlights

Expand All @@ -13,7 +13,8 @@ A voice-powered developer research assistant built for the native.builder hackat

- **Voice input**: tap-to-record microphone with real-time partial transcripts, editable before submit, and re-record.
- **Speechmatics STT**: official real-time client; the session JWT is passed via `client.start()` and never embedded in a URL.
- **Research pipeline**: a Supabase Edge Function runs a search→read→synthesize loop and returns an answer plus clickable source citations; the AI/ML model is selectable per session.
- **Research pipeline**: a Supabase Edge Function (`supabase/functions/research/index.ts`) runs a search→read→synthesize loop and returns an answer plus clickable source citations; the AI/ML model is selectable per session. All inferences are served serverless via AI/ML API.
- **Web search integration**: Bright Data SERP API (Google searches) + Web Unlocker (page fetching) power the research agent.
- **Markdown answers**: assistant responses render as formatted markdown (headings, lists, code, tables, links) with source citations, XSS-safe by default.
- **Persistent conversations**: auth-gated chat history with sequential ordering and real-time updates.
- **Auth**: email/password via Supabase, enforced password policy, and server-side rate limiting on auth endpoints (Supabase-managed).
Expand All @@ -33,11 +34,14 @@ flowchart LR
U -->|sign in| Auth[Supabase Auth]
```

The Edge Function source lives in `supabase/functions/research/index.ts` and is deployed via `supabase functions deploy research`.

## Tech Stack

| Layer | Technology |
| ----- | ---------- |
| UI | React, TypeScript, Vite, Tailwind CSS |
| UI | React 19, TypeScript 7 (via @typescript/native), Vite 8, Tailwind CSS 3 |
| Testing | Vitest 4 |
| Auth & data | Supabase (Auth, Postgres, Realtime, Edge Functions) |
| Speech-to-text | @speechmatics/real-time-client |
| Research | Bright Data (SERP + Web Unlocker), AI/ML API |
Expand All @@ -50,10 +54,24 @@ bun install
bun dev
```

Requirements: a Supabase project with the `speechmatics-token` and `research` Edge Functions and their secrets, plus a valid Bright Data and AI/ML API key. Database types are generated from the Supabase schema in `src/lib/database.types.ts`.
Requirements: a Supabase project with the `research` Edge Function deployed and its secrets (`AIML_API_KEY`, `BRIGHTDATA_API_KEY`), plus a valid Bright Data and AI/ML API key. Database types are generated from the Supabase schema in `src/lib/database.types.ts`.

### Deploying the Edge Function

```bash
supabase functions deploy research
```

The function source is versioned at `supabase/functions/research/index.ts`.

## Validation

- **Typecheck**: `bun run typecheck` — 0 errors
- **Tests**: `bun run test` — 122 tests passing
- **Coverage**: `bun run test -- --coverage` — ~60%+ statements, ~79% branches (thresholds: 25/65/60/25)
- **Lint**: `bun run lint` — 0 errors
- **Build**: `bun run build` — OK

Security posture and system behavior are specified and verified through the SDD in `openspec/` (spec-driven development):

- `security-posture`, `authentication`, `voice-input`, `research-pipeline`, `conversation-management`, `supabase-foundation`, `account-management`, `test-framework`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
schema: spec-driven
created: 2026-08-09
skip_specs: true
32 changes: 32 additions & 0 deletions openspec/changes/archive/2026-08-09-auth-login-rework/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## Context

The app now uses a unified green-accent design system: `accent` #22C55E as the sole identity color, `accent-muted`, `on-accent` (near-black #02140B), `background`, `foreground`, `muted`, `border`, `destructive`. The animated `.waveform`/`.waveform-bar` classes in `src/index.css` are the signature for waiting/idle states. `AuthScreen.tsx` (three modes: signin/signup/forgot) and the `SetNewPassword` component are the only screens still on the legacy slate palette (`bg-dotgrid-glow`, `primary`, `on-primary`, `ring`). See proposal.md - Why.

## Goals / Non-Goals

**Goals:**
- Bring auth screens onto the green-accent token set so they read as part of the same product.
- Give auth a distinct, memorable brand moment (the waveform) without hurting form usability.
- Keep the change TDD-gated with class-level assertions.

**Non-Goals:**
- No change to auth flow, password policy, rate limiting, or validation logic (behavior covered by the `authentication` spec, unchanged).
- No new dependencies, no icon library, no OAuth.
- No redesign of the DevVoice wordmark itself.

## Decisions

- **Retheme rather than rebuild the card.** The existing card layout (tabs, fields, footer links) already works; we swap token classes only. Lower risk than restructuring. Alternative (full redesign) rejected for scope.
- **`bg-dotgrid-glow` -> `bg-background`.** For consistency with the rest of the app (user-selected "consistencia"). The dot-grid texture is removed; a `bg-background` wrapper matches the app shell. Alternative (keep texture) rejected for consistency.
- **Submit button `bg-primary text-on-primary` -> `bg-accent text-on-accent`.** Matches the composer/footer CTA. Keep `hover:opacity-90 active:scale-[0.97]` affordances.
- **Active tab `border-primary` -> `border-accent`** so the selected tab uses the identity color.
- **Waveform as brand moment.** Reuse `.waveform`/`.waveform-bar` above the wordmark. Respect reduced motion: add a `motion-reduce:hidden` guard (or a `prefers-reduced-motion` media query toggle in index.css) so the animation doesn't play for users who disable motion.
- **Contrast risk on `on-accent`.** `on-accent` is near-black; on green it may read as lower contrast than white-on-slate. Default decision: keep `on-accent`. Verify visually; if contrast is poor, flip submit text to `text-background` (near-black, already on the palette) rather than introducing a new color.
- **Consistent control height.** Fields to `h-11` to match the footer composer bar; keep label spacing.

## Risks / Trade-offs

- [Reduced contrast on green button] -> Verify in the browser; fall back to `text-background` if `on-accent` reads too dark on `accent`.
- [Removing dot-grid may flatten the auth screen] -> Acceptable; consistency wins and the waveform restores visual interest.
- [Waveform animation on auth] -> Gate behind `motion-reduce:hidden` to respect reduced-motion preferences.
- [Test assertions on class strings are brittle] -> Scope assertions to the presence of the token substring (`bg-accent`, not the full class list) and disambiguate the "Sign In" tab from the submit button by `querySelector('button[type="submit"]')`.
27 changes: 27 additions & 0 deletions openspec/changes/archive/2026-08-09-auth-login-rework/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Why

The login and signup screens are the first thing a user sees, but they use a leftover slate/"primary" palette (`bg-dotgrid-glow`, `bg-primary`, `border-primary`) that is inconsistent with the green-accent design system now used across the whole app. The auth screens look like they belong to a different product and undercut the cohesive brand moment.

## What Changes

- Retheme `AuthScreen.tsx` and the `SetNewPassword` component off the slate `primary` palette onto the green-accent token set (`accent`, `on-accent`, `border-accent`, `bg-background`).
- Add the animated voice waveform (the app's signature for waiting states) as a brand moment above the DevVoice wordmark on both auth screens.
- Tighten form controls to consistent 44px heights matching the composer bar, and normalize focus affordances to the accent ring.
- No auth flow, validation, or rate-limit behavior changes.

## Capabilities

### New Capabilities

- None.

### Modified Capabilities

- None (visual presentation only; the `authentication` spec's behavioral requirements are unchanged, so this change declares `skip_specs: true`).

## Impact

- `src/components/AuthScreen.tsx` - retheme submit button, active tabs, wrapper background; add waveform brand element to the branding block (both `AuthScreen` and `SetNewPassword`).
- `src/components/__tests__/AuthScreen.test.tsx` - add class-level assertions so the retheme is TDD-gated.
- Possibly `src/index.css` (reuse existing `.waveform`/`.waveform-bar`. If carried over the auth card, ensure reduced-motion respect).
- No dependency or backend changes. UI copy stays English.
18 changes: 18 additions & 0 deletions openspec/changes/archive/2026-08-09-auth-login-rework/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## 1. Auth retheme to green accent

- [x] 1.1 Add class-level test asserting the auth submit button uses `bg-accent` (disambiguate from the "Sign In" tab via `querySelector('button[type="submit"]')`); confirm it fails on the current `bg-primary`.
- [x] 1.2 Retheme `AuthScreen.tsx` submit button `bg-primary text-on-primary` -> `bg-accent text-on-accent` (keep hover/active/disabled affordances); active tab `border-primary` -> `border-accent`; wrapper `bg-dotgrid-glow` -> `bg-background`.
- [x] 1.3 Retheme the `SetNewPassword` component the same way (submit button and wrapper) in `AuthScreen.tsx`.
- [x] 1.4 Run `bun run test AuthScreen`, `bun run typecheck`, `bun run lint`; confirm green.

## 2. Waveform brand moment

- [x] 2.1 Add the animated `.waveform`/`.waveform-bar` element above the DevVoice wordmark in both `AuthScreen` and `SetNewPassword` branding blocks, gated with `motion-reduce:hidden`.
- [x] 2.2 Verify reduced-motion guard (add a `prefers-reduced-motion` toggle in `src/index.css` if `motion-reduce` alone is not enough). Tailwind's `motion-reduce:hidden` maps to a `@media (prefers-reduced-motion: reduce)` rule natively - no extra CSS needed.
- [x] 2.3 Run `bun run test AuthScreen`, `bun run typecheck`, `bun run build -- --outDir /tmp/devvoice-dist`; confirm green.

## 3. Form polish and gate

- [x] 3.1 Normalize auth field inputs to consistent `h-11` height to match the composer bar; keep label spacing.
- [x] 3.2 Run the full gate: `bun run typecheck`, `bun run test -- --coverage` (above thresholds 25/65/60/25), `bun run lint`, `bun run build -- --outDir /tmp/devvoice-dist`.
- [x] 3.3 Residue scan: confirm no `bg-primary`/`border-primary`/`on-primary`/`bg-dotgrid-glow` remain in `src/components/AuthScreen.tsx`.
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
## 1. Dependencies

- [ ] 1.1 Add `react-markdown` and `remark-gfm` via `bun add react-markdown remark-gfm`
- [x] 1.1 Add `react-markdown` and `remark-gfm` via `bun add react-markdown remark-gfm`

## 2. Markdown rendering

- [ ] 2.1 Modify `src/components/MessageList.tsx` to render assistant content with `<Markdown remarkPlugins={[remarkGfm]}>` and keep user bubbles plain text
- [ ] 2.2 Add a `components` override so assistant markdown links open in a new tab with `rel="noopener noreferrer"`
- [ ] 2.3 Adjust assistant bubble whitespace and add minimal dark-theme markdown styling in `src/index.css`
- [ ] 2.4 Update `src/components/__tests__/MessageList.test.tsx` to assert markdown is interpreted (e.g. `**bold**` renders as `<strong>`) and user content stays plain
- [x] 2.1 Modify `src/components/MessageList.tsx` to render assistant content with `<Markdown remarkPlugins={[remarkGfm]}>` and keep user bubbles plain text
- [x] 2.2 Add a `components` override so assistant markdown links open in a new tab with `rel="noopener noreferrer"`
- [x] 2.3 Adjust assistant bubble whitespace and add minimal dark-theme markdown styling in `src/index.css`
- [x] 2.4 Update `src/components/__tests__/MessageList.test.tsx` to assert markdown is interpreted (e.g. `**bold**` renders as `<strong>`) and user content stays plain

## 3. Model selector

- [ ] 3.1 Add optional `model` param to `runResearch` in `src/hooks/useResearch.ts` and append it to the invoke body only when set
- [ ] 3.2 Update `src/hooks/__tests__/useResearch.test.ts` to cover model forwarding and the no-model case
- [ ] 3.3 Create `src/components/ModelSelector.tsx` with a `<select>` of candidate AI/ML models mirroring LanguageToggle
- [ ] 3.4 Add per-session model state to `src/components/ConversationView.tsx`, pass it to `runResearch`, and render `<ModelSelector>`
- [x] 3.1 Add optional `model` param to `runResearch` in `src/hooks/useResearch.ts` and append it to the invoke body only when set
- [x] 3.2 Update `src/hooks/__tests__/useResearch.test.ts` to cover model forwarding and the no-model case
- [x] 3.3 Create `src/components/ModelSelector.tsx` with a `<select>` of candidate AI/ML models mirroring LanguageToggle
- [x] 3.4 Add per-session model state to `src/components/ConversationView.tsx`, pass it to `runResearch`, and render `<ModelSelector>`

## 4. Optimistic sidebar

- [ ] 4.1 Modify `createConversation` in `src/hooks/useConversations.ts` to select the inserted row and prepend it to local `conversations` state
- [ ] 4.2 Create `src/hooks/__tests__/useConversations.test.ts` asserting the new item appears immediately after create
- [x] 4.1 Modify `createConversation` in `src/hooks/useConversations.ts` to select the inserted row and prepend it to local `conversations` state
- [x] 4.2 Create `src/hooks/__tests__/useConversations.test.ts` asserting the new item appears immediately after create

## 5. Verification

- [ ] 5.1 Run `bun run typecheck`, `bun run lint`, `bun run test` and confirm all pass
- [x] 5.1 Run `bun run typecheck`, `bun run lint`, `bun run test` and confirm all pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
schema: spec-driven
created: 2026-08-08
skip_specs: true
33 changes: 33 additions & 0 deletions openspec/changes/archive/2026-08-09-ui-ux-normalization/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Context

The chat UI currently lives ad-hoc: `MessageList.tsx` renders role-aware bubbles, sources, the researching indicator, and auto-scroll, while `ConversationView.tsx` owns the input + Send composer beside `VoiceInput`. Both are bespoke with duplicated styling and no shared contract. The data layer is custom and working: conversations/messages persist in Supabase, `useResearch` calls the `research` Edge Function returning `{answer, sources}`. The app uses React 19, Vite, Tailwind 3, with CSP/SRI enforced, and must stay dependency-light (memory-limited container).

External frameworks (assistant-ui, NLUX, chatscope) were researched and rejected because they force a runtime/thread state model that conflicts with the existing Supabase schema and a standard composer that fights the custom voice input.

## Goals / Non-Goals

**Goals:**
- One source of truth for bubble look, message-with-sources, the chat scroll/typing container, and the text composer.
- Reuse across the app by extracting the chat into `src/components/chat/` primitives.
- Preserve identical rendered behavior, props, and `data-testid`s so existing tests pass unchanged.
- Zero new dependencies.

**Non-Goals:**
- No external chatbot framework.
- No data-layer, persistence, `research` EF, or `VoiceInput` changes.
- No streaming or generative tool-call UI.

## Decisions

1. **Internal component extraction over a framework.** Four small primitives expose the chat contract without a dependency tree or a foreign state model. Each maps to an isolated unit of UI.
2. **Keep the `MessageList` public name.** Existing consumers and `MessageList.test.tsx` reference `MessageList` and the `message-${role}` testids; the component becomes a thin wrapper over `ChatList` so nothing downstream changes.
3. **`ChatBubble` owns markdown + anchor override.** The `target="_blank" rel="noopener noreferrer"` anchor override moves from `MessageList` into `ChatBubble`; markdown uses `react-markdown` + `remark-gfm`, no `rehype-raw` (XSS-safe).
4. **`ChatComposer` owns the empty guard.** The `trim().length === 0` short-circuit moves from `ConversationView` into the composer, so the row is self-contained and testable. `ChatComposer` does NOT absorb `VoiceInput`.
5. **`ChatList` owns scroll + states.** Auto-scroll (`scrollIntoView` on `[messages, researching]`), empty state, loading state, and the `Researching…` indicator all live here.

## Risks / Trade-offs

- **Wrapper indirection**: `MessageList` delegating to `ChatList` adds one layer; kept intentionally thin to avoid breaking existing imports/tests.
- **Refactor risk**: moving styling/state must preserve pixel-identical output. Mitigation: existing `MessageList.test.tsx` (10 tests) acts as a regression guard; run full suite after rewire.
- **Guard ownership**: moving the empty-input guard changes where submit behavior is enforced. New `ChatComposer.test.tsx` covers it; `ConversationView` keeps its own `trim()` check harmlessly.
- **Voice composer gap**: the mic stays bespoke in `VoiceInput`, so the text and voice paths are normalized separately. Accepted trade-off to preserve hold-to-record UX.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## Why

The chat interface is hand-rolled and duplicated across `MessageList.tsx` and `ConversationView.tsx`, so bubble styling, the typing indicator, auto-scroll, and the composer are maintained in two places with no shared contract. This makes UX iteration fragile and prevents reuse. External chatbot frameworks (assistant-ui, NLUX, chatscope) were evaluated and rejected: they pull a large dependency tree and force a runtime/state model that conflicts with the existing Supabase-driven conversations/messages and the custom voice composer.

## What Changes

- Extract the chat UI into reusable internal primitives under `src/components/chat/`: `ChatBubble`, `ChatMessage`, `ChatList`, `ChatComposer`, plus a barrel `index.ts`.
- `ChatBubble` owns the role-aware bubble look (user right/blue, assistant left/grey + markdown) and the markdown anchor override.
- `ChatMessage` composes a `ChatBubble` with the assistant source-citation list.
- `ChatList` owns the scroll container, empty/loading states, and the `Researching…` typing indicator.
- `ChatComposer` owns the controlled text input + Send row with the empty/whitespace guard.
- `MessageList.tsx` becomes a thin pass-through to `ChatList`, preserving its props and `message-user`/`message-assistant` testids.
- `ConversationView.tsx` uses `ChatComposer` next to `VoiceInput`, preserving the current `items-center` alignment.
- No behavior change to data hooks (`useMessages`, `useResearch`, `useConversations`), Supabase schema, the `research` Edge Function, or `VoiceInput` internals. No new dependencies.

## Capabilities

### New Capabilities

None. This is a pure refactor: behavior of the rendered chat is unchanged (bubbles, typing indicator, composer, sources render identically). `skip_specs: true` is set in `.openspec.yaml` per the spec-driven schema.

### Modified Capabilities

None. No spec-level behavior changes.

## Non-Goals

- Not adopting any external chatbot framework.
- Not changing the data layer, persistence, or the `research` Edge Function.
- Not absorbing `VoiceInput` (hold-to-record + language toggle) into the composer.
- Not adding streaming or generative tool call UI.

## Impact

- **Code**: `src/components/chat/` (new), `src/components/MessageList.tsx` (shrinks to wrapper), `src/components/ConversationView.tsx` (uses `ChatComposer`).
- **Tests**: new unit tests per primitive; existing `MessageList.test.tsx` and all current suites stay green.
- **Dependencies**: none added or removed.
- **Systems**: no backend, Supabase, or Edge Function changes.
Loading
Loading