Back to the catalog

vue-pwa-starter knowledge

Bundle OKF 0.2 · 0 conceitos · alexanderop/rowing-workout-plan

Open source Repository Open in the app JSON README (API)

About

# vue-pwa-starter knowledge

## What this is

A local-first Vue 3 PWA starter template. Data lives in the browser (Dexie/IndexedDB), with no backend and no accounts. Mobile-first: the app shell, safe-area handling, and keyboard-aware sheets are the product. The `notes` feature is a worked example meant to be copied and then deleted.

When in doubt about a design call: does it keep interactions instant and the data on-device?

## How to read this

This file is the entry point. It holds the rules; the concept files it links hold the reasoning behind them, as an [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md) v0.2 bundle. One concept per markdown file, YAML frontmatter (`type`, `title`, `description`, `tags`, `status`), relative links between them. Follow a link when your task touches it; do not read the tree top to bottom.

There is no `CLAUDE.md` and no `AGENTS.md`. A `SessionStart` hook (`.claude/hooks/docs.mjs`) injects this file

Details

Kind
OKF bundles
Topic
AI, RAG & memory
Publisher
alexanderop
Origin
okf_github
Category
dados
Version
0.2
Open pull requests
1
Last push
2026-08-24T19:39:32Z
Repository state
ativo
Language
TypeScript
License
MIT
Added
2026-09-09 19:04:11
Updated
2026-09-09 19:04:11
Origin id
alexanderop/rowing-workout-plan:docs/index.md

README

# vue-pwa-starter

A **local-first Vue 3 PWA starter** with the part most starters skip: a complete, tiered testing strategy and the mobile app-shell boilerplate you otherwise rebuild every time.

Everything lives on the device (IndexedDB via Dexie). No backend, no accounts, no spinners. Built for apps you use on a phone, offline, mid-task.

## Quickstart

Use this repository as a GitHub template (or clone it), then:

```bash
pnpm install
pnpm dev
```

All gates should be green out of the box:

```bash
pnpm check                                        # everything below that needs no browser, in parallel (~8 s)
pnpm test && pnpm test:a11y && pnpm test:visual   # browser + a11y + visual tiers
pnpm test:e2e                                     # production build in a real browser
pnpm test:mutation                                # mutation score over the unit tier (~10 s)
pnpm build && pnpm size-limit                     # bundle budget
```

`pnpm check` is the one to reach for while you work: it runs lint, formatting,
types, dead-code, the unit tier and the architecture tier concurrently, keeps
going after the first failure, and prints each task's output under its own
label — so one run tells you everything that is broken.

## What you get

| Area             | What is in the box                                                                                                                                                                                                                                                                                                                                 |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App shell        | Config-driven bottom nav ([`OrganismAppShell.vue`](src/components/organisms/OrganismAppShell.vue)), optional center FAB slot, `meta.hideNav` escape hatch, safe-area insets, `TemplatePageLayout`/`MoleculePageHeader`, keyboard-aware bottom sheet (`MoleculeDialogContent`), toast viewport                                                      |
| UI components    | shadcn-vue-style primitives over [Reka UI](https://reka-ui.com/), copied rather than installed — compound parts, `cn()` class merging, `data-slot` targeting, `as-child`. The layer boundary is lint- and test-enforced. See [docs/ui-components.md](docs/ui-components.md)                                                                        |
| Local-first data | Dexie schema with a converter pattern for reading old data forever, repository layer, schema-validated JSON export/import                                                                                                                                                                                                                          |
| Testing          | Six tiers: unit (Node, ~100 ms), browser (Vitest browser mode), a11y (axe-core), visual (screenshots), architecture (ArchUnitTS), e2e (playwright-bdd against the production build). See [docs/testing-strategy.md](docs/testing-strategy.md)                                                                                                      |
| Test quality     | Stryker mutation testing scoped to the unit tier — grades whether the assertions would notice a bug, not whether the lines ran. Runs in ~10 s, own CI job. See [docs/mutation-testing.md](docs/mutation-testing.md)                                                                                                                                |
| Quality gates    | oxlint + ESLint + Prettier + markdownlint, knip (dead exports), size-limit (bundle budget), husky pre-commit gate (~15 s)                                                                                                                                                                                                                          |
| PWA              | vite-plugin-pwa with update prompt, icons generated from one SVG at build time, offline precache, web-vitals seam                                                                                                                                                                                                                                  |
| Observability    | Every db operation is a named Effect span already; opt into OTLP export in development with one env var — no `@opentelemetry/*` dependency, nothing in the production bundle. See [Tracing in development](#tracing-in-development)                                                                                                                |
| CI               | Sharded GitHub Actions pipeline, actions pinned by SHA, zizmor-clean                                                                                                                                                                                                                                                                               |
| i18n             | vue-i18n with typed message keys, English + German                                                                                                                                                                                                                                                                                                 |
| Agent-ready      | No `CLAUDE.md`, no `AGENTS.md` — [`docs/`](docs/index.md) is an [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md) bundle of concept files (including a full Effect v4 reference), and a `SessionStart` hook injects its index into every agent session. Humans and agents read the same file |

## Stack

Vue 3.5 · TypeScript (strict) · Vite · Tailwind CSS v4 · reka-ui · VueUse · Dexie · Effect v4 · vue-router · vue-i18n · Vitest 4 (browser mode) · Playwright · pnpm (with catalogs — pnpm is required)

## Philosophy: local-first

Design tie-breakers, borrowed from [Ink & Switch's local-first ideals](https://www.inkandswitch.com/local-first/):

1. **No spinners** — instant input, never block on network
2. **Network optional** — fully offline
3. **The Long Now** — data readable after the app dies (export, schema stability, converters)
4. **Ownership & control** — user owns the data, no accounts, exportable any time

How the data layer implements this: [docs/local-first.md](docs/local-first.md)

## Project structure

```text
src/features/      Feature-owned UI, state, and domain logic (features never import features)
src/db/            Dexie schema, converters, repositories — the only place that touches storage
src/stores/        Shared app-wide state (@effect/atom-vue atoms, not Pinia)
src/composables/   Shared reactive logic (2+ consumers)
src/views/         Route-level pages; may compose multiple features
src/components/    Shared components, tiered by atomic design — docs/atomic-design.md
                   atoms/ molecules/ organisms/ templates/; every atom is a styled
                   primitive (shadcn-style, yours to edit — docs/ui-components.md),
                   as is a directory with a barrel; a flat .vue above atoms/ is a
                   composite
src/__tests__/     All tests, mirroring the source tree — not colocated
test/e2e/          playwright-bdd features + steps
```

These boundaries are not just documentation — they are enforced by [architecture tests](src/__tests__/architecture/architecture.test.ts) over the module graph and by `no-restricted-imports` rules in [eslint.config.ts](eslint.config.ts), which also cover `.vue` files. [A negative test](src/__tests__/architecture/boundaries.test.ts) proves the enforcement actually fires.

## Tracing in development

The instrumentation is already there: every repository operation is wrapped in a named `Effect.fn`, the backup programs add `Effect.withSpan`, and every reported failure emits a log record annotated with `boundary` / `operation` / `failure`. What is missing by default is somewhere to send it.

Start a collector and point the app at it:

```bash
docker run --rm -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one
cp .env.example .env.local
pnpm dev
```

Open <http://localhost:16686>, pick the `vue-pwa-starter` service, and you get a span per db operation — including the create → list pair that shows a write invalidating a reactivity key and the read atom re-reading from disk.

Three things make this cheap enough to ship in a starter:

- The OTLP exporters live in `effect/unstable/observability` and post JSON over `fetch`, so there is no `@opentelemetry/*` SDK to install or bundle.
- The dev server proxies `/_otlp` to `localhost:4318`, keeping the request same-origin — a stock collector rejects the CORS preflight an OTLP payload would otherwise trigger.
- `import.meta.env.DEV` is a literal `false` in a production build, so the exporter is dead code. `pnpm size-limit` is what keeps that honest, and telemetry about a user's own data never has the chance to leave their device.

## Adding your first feature

Follow the walkthrough in [docs/adding-a-feature.md](docs/adding-a-feature.md), which goes layer by layer and names the test tier each step belongs in.

The example feature the walkthrough was written against — a notes screen that touched every layer — has been removed, so `src/features/` is empty and `/` opens Settings. The `docs/` concept files still teach through it; read them for the *rule*, and git history (`git show HEAD~1`) for the code. Each of them is rewritten against the real feature as the slices in [`specs/`](specs/) land.

## License

[MIT](LICENSE)

More