Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 3 additions & 3 deletions .claude/agents/qa.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ claims, including regression paths, loading/empty/error states, mobile
layouts, and denied access where relevant.

- Unit/integration: the narrowest `vp run test` scope that covers the slice.
- Browser: `just e2e-local` (`vp run test:e2e:local`) against Vite on
`http://localhost:5173`, not the Convex site proxy. It needs a running
- Browser: `just journeys` (`vp run test:journeys`) runs the browser-mode
journeys in `apps/web/e2e` against the local backend. It needs a running
`just dev` stack; if none is running and you were not told you own one,
report that instead of starting it.
- Log in with the seeded demo owner `demo@groam.example` / `GroamDemo123!`.
Never reset or reseed data.
- Adding coverage: read `add-web-view-test`, `add-convex-test`, or
`add-test-id` first, and edit only test files the brief names.

Use `rg` to find existing specs under `apps/web/e2e/` and tests near the
Use `rg` to find existing journeys under `apps/web/e2e/` and tests near the
changed code. No fabricated screenshots, skipped failures, or arbitrary sleeps.

Return per criterion: pass/fail, reproducible steps, actual versus expected.
Expand Down
6 changes: 5 additions & 1 deletion .claude/skills/add-convex-test/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description: >-
# Add a Convex test

Colocate `<topic>.test.ts` next to the module under
`packages/backend/convex/modules/...` (Convex excludes `*.test.ts` from
`packages/backend/convex/modules/<domain>/` (Convex excludes `*.test.ts` from
deploys). Import test APIs from `vite-plus/test`, not `vitest`.

## Setup
Expand Down Expand Up @@ -62,6 +62,10 @@ test('adds an activity to a scheduled destination', { timeout: 15_000 }, async (
- Tests go through `client.query` / `client.mutation` / `client.action`;
`ctx.db` stays in modules and routes.
- Use `{ timeout: 15_000 }` as the floor for authenticated fixtures.
- Scheduled functions wait on fake timers; drive them with
`finishAllScheduledFunctions(vi.runAllTimers)` or call `vi.useRealTimers()`.
- Console output fails the test unless declared with `expectConsole(level,
pattern)` from `@groam/vitest-config/console`.

## Run

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-env-variable/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ scripts.
| `runtime` | installed-app runtime | `GROAM_CONVEX_URL`, `GROAM_DATA_DIR` |
| `desktop-tooling` | `apps/desktop/scripts` | `VITE_PHOTON_URL`, `CI` |
| `seeder` | `tooling/seeder` | `SEED_CONVEX_URL`, `SEED_USER_EMAIL` |
| `playwright` | web E2E | `PLAYWRIGHT_BASE_URL`, seed credentials |
| `journeys` | browser journeys (`apps/web/e2e`) | `VITE_CONVEX_URL`, `VITE_CONVEX_SITE_URL` |
| `showcase` | `tooling/showcase` | `SHOWCASE_*` |
| `vitest` | `tooling/vitest-config` | `CI` |

Expand Down
23 changes: 14 additions & 9 deletions .claude/skills/add-test-id/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: add-test-id
description: >-
Add a stable data-testid for Groam Playwright and Testing Library locators.
Add a stable data-testid for Groam browser-mode view tests and journeys.
Use when a new control, dialog, row, or empty-state action needs a test
hook — ids live in @groam/app-actions/ids, never as inline strings in one
app.
Expand All @@ -10,7 +10,7 @@ description: >-
# Add a test id

Stable locators are the `ids` object in
`tooling/app-actions/src/playwright/ids.ts`, published as
`tooling/app-actions/src/testing/ids.ts`, published as
`@groam/app-actions/ids`. The web app re-exports it as `testIds` from
`@/lib/test-ids`.

Expand All @@ -31,13 +31,18 @@ Stable locators are the `ids` object in
<Button data-testid={testIds.agentStop}>Stop</Button>
```

3. Query the same key in tests: `screen.getByTestId(testIds.agentStop)` in
Testing Library (`add-web-view-test`), `page.getByTestId(ids.agentStop)`
in Playwright.
4. For a repeated browser journey, add or reuse a helper in
`tooling/app-actions/src/playwright/<verb>.ts` (exported from its
`index.ts`) instead of repeating the steps in a spec under
`apps/web/e2e/`.
3. Query the same key in tests: `page.getByTestId(testIds.agentStop)` in
view tests (`add-web-view-test`) and journeys.
4. For a repeated browser step, add or reuse a helper in
`tooling/app-actions/src/vitest/<verb>.ts` (exported from its
`index.ts`) built on `page` locators from `vite-plus/test/browser`,
instead of repeating the steps in a test.
5. View tests and journeys use the same steps. Journeys
(`apps/web/e2e/**/*.test.tsx`) mount the whole app with `openApp(path)`
from `@/testing/open-app`, signed in as the file's own account and group;
`arrange` from `@groam/app-actions/vitest` creates trips, ideas, issues,
and members through the backend, and other people act through it too.
Drive only the journey under test through the UI.

## Rules

Expand Down
55 changes: 39 additions & 16 deletions .claude/skills/add-web-view-test/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: add-web-view-test
description: >-
Add a Vitest + Testing Library test for a Groam web feature view. Use when
Add a Vite+ browser-mode test for a Groam web feature view. Use when
covering a React screen, loading/empty/error state, dialog, or sidebar row
in apps/web — mock feature hooks and agent context, never spin up Convex
(use add-convex-test for backend behavior).
Expand All @@ -10,15 +10,18 @@ description: >-
# Add a web view test

Colocate `<name>.test.tsx` next to the component in
`apps/web/src/features/<domain>/`. Mock the feature hooks, workspace state,
router, and agent context; render the view; assert roles, copy, and test
ids.
`apps/web/src/features/<domain>/`. View tests run in real Chromium (the
`views` browser setup of `vp test`) with the app's styles. Mock the feature
hooks, workspace state, router, and agent context; render the view; drive it
with `page` locators; assert with `expect.element`, which retries. Testing
Library and jsdom are not used.

## Pattern (from `agents/agent-list/agents-list-view.test.tsx`)

```tsx
import { cleanup, render, screen } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vite-plus/test';
import { render } from '@groam/vitest-config/react';
import { beforeEach, describe, expect, test, vi } from 'vite-plus/test';
import { page } from 'vite-plus/test/browser';
import { testIds } from '@/lib/test-ids';
import { AgentsListView } from './agents-list-view';

Expand Down Expand Up @@ -51,23 +54,39 @@ beforeEach(() => {
state.isLoading = false;
state.runs = [];
});
afterEach(cleanup);

describe('AgentsListView', () => {
test('shows a labeled loading state', () => {
test('shows a labeled loading state', async () => {
state.isLoading = true;
render(<AgentsListView />);
expect(screen.getByRole('status', { name: 'Loading agent runs…' })).toBeTruthy();
await render(<AgentsListView />);
await expect.element(page.getByRole('status', { name: 'Loading agent runs…' })).toBeVisible();
});

test('shows the empty state', () => {
render(<AgentsListView />);
expect(screen.getByText('Nothing running yet')).toBeTruthy();
expect(screen.queryByTestId(testIds.agentsRoster)).toBeNull();
test('shows the empty state', async () => {
await render(<AgentsListView />);
await expect.element(page.getByText('Nothing running yet')).toBeVisible();
await expect.element(page.getByTestId(testIds.agentsRoster)).not.toBeInTheDocument();
});
});
```

`render` and `renderHook` come from `@groam/vitest-config/react` and unmount
after each test. Interact like a user: `await page.getByLabelText(...).fill()`,
`.click()`. For many input combinations, drive the view through the shared
steps in `@groam/app-actions/vitest` (for example `createTrip(input)`) with
`test.each`, as `trip-create/create-trip-dialog.test.tsx` does.

```tsx
test.each<[string, CreateTripFormInput]>([
['only a name', { name }],
['a budget', { budget: 2500, name }]
])('creates a trip with %s', async (_, input) => {
await render(<CreateTripDialog createTrip={createTrip} onClose={vi.fn()} onCreated={vi.fn()} />);
await fillCreateTrip(input);
await expect.poll(() => createTrip.mock.calls.length).toBe(1);
});
```

Reuse existing mocks before writing new ones: `@/testing/stub-match-media`,
`@/testing/trip-planner-fixture`, `features/trips/test-workspace-mocks.tsx`,
`features/agents/agent-test-router-mock.tsx`. Mock workspace state at
Expand All @@ -80,8 +99,12 @@ Reuse existing mocks before writing new ones: `@/testing/stub-match-media`,
`@groam/app-actions/ids` (`add-test-id`).
- Hook logic gets its own `hooks/use-<name>.test.ts(x)`; `packages/ui`
primitives get a `*.test.tsx` beside the primitive.
- Console output fails the test unless declared with `expectConsole(level,
pattern)` from `@groam/vitest-config/console`; never silence it with
`vi.spyOn(console, …)`.

## Run

From `apps/web`: `vp test run src/features/<domain>/<name>.test.tsx`.
Whole repo: `just test`.
From `apps/web`: `vp test run --project views src/features/<domain>/<name>.test.tsx`.
Whole repo: `just test`. Full flows against the backend are journeys
(`apps/web/e2e`, `just journeys`); keep those few and cover combinations here.
5 changes: 4 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ COPY --from=uv /uv /uvx /usr/local/bin/
COPY --from=just /just /usr/local/bin/

# Tauri 2 Linux prerequisites (https://tauri.app/start/prerequisites/) plus the
# extra packages from tauri-apps/tauri-action's GitHub workflow.
# extra packages from tauri-apps/tauri-action's GitHub workflow. libnss3 and
# libnspr4 are the only libraries Playwright's Chromium needs beyond them.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
file \
libayatana-appindicator3-dev \
libgtk-3-dev \
libnspr4 \
libnss3 \
librsvg2-dev \
libssl-dev \
libwebkit2gtk-4.1-dev \
Expand Down
4 changes: 4 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ sudo chown -R "$(id -u):$(id -g)" "${persistent_dirs[@]}"

VP_GIT_HOOKS=0 vp install --frozen-lockfile

# Browser-mode tests (`vp test`) need the Chromium build matching the locked
# Playwright provider; its system libraries come from the Dockerfile.
vp run test:browsers:install

# Interactive `claude` in bash and zsh uses the repository harness (Headroom,
# Caveman, MCP). An alias, not a PATH shim, so the harness still finds the real
# binary. Home is rebuilt with the container, so this reruns on every rebuild;
Expand Down
23 changes: 13 additions & 10 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,25 @@ just desktop

## Checks

Run what CI runs before opening a pull request:
Run the CI gate before opening a pull request:

```bash
just check
```

`just check` covers audit, typecheck, lint, unit tests, React Doctor, and
Fallow. CI also runs the browser journeys (`just journeys`, below) and the
showcase recording (`bun run --cwd tooling/showcase test:browser`), and builds
the desktop app on each platform; `just check` runs none of those.

Smaller loops:

| Command | What it does |
| ---------------- | ---------------------------------- |
| `just lint` | Conventions, syncpack, `vp check` |
| `just typecheck` | TypeScript |
| `just test` | Unit tests |
| `just e2e-local` | Playwright against Vite on `:5173` |
| Command | What it does |
| ---------------- | ----------------------------------------------------------------------------- |
| `just lint` | Conventions, Claude harness, syncpack, `vp check` |
| `just typecheck` | TypeScript |
| `just test` | Unit and view tests |
| `just journeys` | Browser journeys against the running local backend (start it with `just dev`) |

## Search the code graph

Expand All @@ -85,8 +90,6 @@ just graphify-query "trip access planner" # matches identifiers, not prose

Run `just graphify-refresh` after changing source files. The generated
`graphify-out/` directory is intentionally local and ignored by Git.


Graphify and its isolated Python environment are part of the container image;
nothing is installed into the host or the Bun workspace.

Expand All @@ -95,7 +98,7 @@ Do not hand-edit `apps/web/src/routeTree.gen.ts`. Add routes as files under

## Conventions

- Import through package aliases (`@groam/ui`, `@groam/ai-contracts`, `@/features/…`),
- Import through package aliases (`@groam/ui`, `@groam/app-actions`, `@/features/…`),
never parent `../` paths across packages
- Semantic color tokens only (no hex / rgb / Tailwind gradients in features)
- Public Convex functions live under `packages/backend/convex/routes` as
Expand Down
17 changes: 17 additions & 0 deletions .github/actions/setup-chromium/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Setup Chromium
description: Install the Chromium build that Vite+ browser-mode tests drive, cached by lockfile

runs:
using: composite
steps:
- name: Cache Chromium
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: chromium-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
chromium-${{ runner.os }}-

- name: Install Chromium
shell: bash
run: bun run test:browsers:install --with-deps
11 changes: 1 addition & 10 deletions .github/actions/setup-workspace/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,12 @@ runs:
bun-version: '1.3.11'

- name: Cache Bun downloads
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: bun-${{ runner.os }}-

- name: Cache Turbo
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ hashFiles('bun.lock') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-${{ hashFiles('bun.lock') }}-
turbo-${{ runner.os }}-

- name: Install dependencies
shell: bash
env:
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/bootstrap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ env:
GIT_CONFIG_COUNT: '1'
GIT_CONFIG_KEY_0: init.defaultBranch
GIT_CONFIG_VALUE_0: main
TURBO_TELEMETRY_DISABLED: '1'

jobs:
provision:
name: Create Convex project and environments
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
Expand Down Expand Up @@ -55,7 +54,7 @@ jobs:
deploy:
name: Bring ${{ matrix.environment }} online
needs: [provision, test]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 25
strategy:
fail-fast: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ permissions:
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/deploy-site-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ env:
GIT_CONFIG_COUNT: '1'
GIT_CONFIG_KEY_0: init.defaultBranch
GIT_CONFIG_VALUE_0: main
TURBO_TELEMETRY_DISABLED: '1'

jobs:
deploy:
name: Deploy ${{ inputs.site }} to ${{ inputs.environment }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15
environment:
name: ${{ inputs.environment }}
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ env:
GIT_CONFIG_COUNT: '1'
GIT_CONFIG_KEY_0: init.defaultBranch
GIT_CONFIG_VALUE_0: main
TURBO_TELEMETRY_DISABLED: '1'

jobs:
test:
Expand All @@ -33,7 +32,7 @@ jobs:
backend:
name: Deploy backend and migrations to ${{ inputs.environment }}
needs: [test]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15
environment:
name: ${{ inputs.environment }}
Expand Down Expand Up @@ -98,7 +97,7 @@ jobs:
smoke-test:
name: Smoke test ${{ inputs.environment }}
needs: [backend, sites]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 5
environment:
name: ${{ inputs.environment }}
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/migrate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ env:
GIT_CONFIG_COUNT: '1'
GIT_CONFIG_KEY_0: init.defaultBranch
GIT_CONFIG_VALUE_0: main
TURBO_TELEMETRY_DISABLED: '1'

jobs:
migrate:
name: Run ${{ inputs.environment }} migrations
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
environment:
name: ${{ inputs.environment }}
Expand Down
Loading
Loading