From e7d4bb109af11d20ab356a5a6ec52374357f2e96 Mon Sep 17 00:00:00 2001 From: franzap <_@franzap.com> Date: Sat, 7 Mar 2026 20:46:02 -0300 Subject: [PATCH] Improve AI guidelines --- .cursor/rules/architecture.mdc | 1 + .cursor/rules/flutter.mdc | 65 ++++++++++++++++ .cursor/rules/invariants.mdc | 1 + .cursor/rules/quality-bar.mdc | 1 + .cursor/rules/vision.mdc | 1 + .cursorrules | 1 - AGENTS.md | 69 +++++------------ spec/guidelines/ARCHITECTURE.md | 5 ++ spec/guidelines/INVARIANTS.md | 5 ++ spec/guidelines/QUALITY_BAR.md | 25 ++++++- spec/guidelines/VISION.md | 5 ++ spec/knowledge/_TEMPLATE.md | 74 +++++++++++++++++++ .../work}/WORK-001-package-manager.md | 0 .../work}/WORK-002-batch-progress.md | 0 .../WORK-004-background-notifications.md | 0 .../work}/WORK-005-updates-screen.md | 0 {work => spec/work}/_TEMPLATE.md | 4 + 17 files changed, 205 insertions(+), 52 deletions(-) create mode 120000 .cursor/rules/architecture.mdc create mode 100644 .cursor/rules/flutter.mdc create mode 120000 .cursor/rules/invariants.mdc create mode 120000 .cursor/rules/quality-bar.mdc create mode 120000 .cursor/rules/vision.mdc delete mode 120000 .cursorrules create mode 100644 spec/knowledge/_TEMPLATE.md rename {work => spec/work}/WORK-001-package-manager.md (100%) rename {work => spec/work}/WORK-002-batch-progress.md (100%) rename {work => spec/work}/WORK-004-background-notifications.md (100%) rename {work => spec/work}/WORK-005-updates-screen.md (100%) rename {work => spec/work}/_TEMPLATE.md (94%) diff --git a/.cursor/rules/architecture.mdc b/.cursor/rules/architecture.mdc new file mode 120000 index 0000000..7a5edd7 --- /dev/null +++ b/.cursor/rules/architecture.mdc @@ -0,0 +1 @@ +../../spec/guidelines/ARCHITECTURE.md \ No newline at end of file diff --git a/.cursor/rules/flutter.mdc b/.cursor/rules/flutter.mdc new file mode 100644 index 0000000..b833785 --- /dev/null +++ b/.cursor/rules/flutter.mdc @@ -0,0 +1,65 @@ +--- +description: Flutter/Dart conventions for Zapstore projects +globs: ["**/*.dart", "**/pubspec.yaml"] +--- + +# Flutter/Dart Conventions + +## State Management + +- Use Flutter Hooks (`HookWidget`, `HookConsumerWidget`) for local widget state — not `StatefulWidget`. +- Use `useState`, `useAnimationController`, `useEffect` over manual `State` classes. +- Use Riverpod providers for shared/global state. Keep providers focused and composable. +- Prefer `ref.watch` for reactive UI, `ref.read` in callbacks only. + +## Data & Async + +- All async work must be cancellable. Pass `CancellationToken` or use `ref.onDispose`. +- Never `await` inside `build()`. Move async work to providers or `useEffect`. +- Use `switch` on sealed classes / union types for exhaustive state handling: + ```dart + return switch (state) { + StorageLoading() => const CircularProgressIndicator(), + StorageError(:final exception) => Text('Error: $exception'), + StorageData(:final models) => MyWidget(models), + }; + ``` +- All async operations must have explicit loading, success, and error states in the UI. + +## Widget Structure + +- One widget per file. File name matches widget name in snake_case. +- Keep `build()` methods short — extract sub-widgets or use helper methods. +- Widgets must not manage relay connections, storage, or background jobs. +- Use `const` constructors wherever possible. + +## Naming + +- Files: `snake_case.dart`. Classes: `PascalCase`. Variables/methods: `camelCase`. +- Providers: `Provider` or `NotifierProvider`. +- Services: `Service`. Notifiers: `Notifier`. + +## Error Handling + +- Never swallow exceptions silently. Surface errors to the UI or log them. +- Use typed exceptions where the caller needs to distinguish error types. +- `try/catch` at the boundary (provider/service), not deep in domain logic. + +## Testing + +- Test providers and services, not widget internals. +- Use `ProviderContainer` for unit-testing Riverpod providers. +- Mock external dependencies (storage, network) — no real I/O in unit tests. +- Widget tests for critical UI states: loading, error, empty, success. + +## Dependencies + +- Run `fvm flutter pub get` after any `pubspec.yaml` change. +- Pin Flutter SDK version via FVM (`fvm use `). +- Prefer packages already in use over adding new ones. + +## Build + +- Assume Android as default target unless instructed otherwise. +- Release builds must be reproducible (see INVARIANTS.md). +- Run `flutter analyze` — fix all issues before committing. diff --git a/.cursor/rules/invariants.mdc b/.cursor/rules/invariants.mdc new file mode 120000 index 0000000..48d477c --- /dev/null +++ b/.cursor/rules/invariants.mdc @@ -0,0 +1 @@ +../../spec/guidelines/INVARIANTS.md \ No newline at end of file diff --git a/.cursor/rules/quality-bar.mdc b/.cursor/rules/quality-bar.mdc new file mode 120000 index 0000000..7946494 --- /dev/null +++ b/.cursor/rules/quality-bar.mdc @@ -0,0 +1 @@ +../../spec/guidelines/QUALITY_BAR.md \ No newline at end of file diff --git a/.cursor/rules/vision.mdc b/.cursor/rules/vision.mdc new file mode 120000 index 0000000..c3dece3 --- /dev/null +++ b/.cursor/rules/vision.mdc @@ -0,0 +1 @@ +../../spec/guidelines/VISION.md \ No newline at end of file diff --git a/.cursorrules b/.cursorrules deleted file mode 120000 index fa62d27..0000000 --- a/.cursorrules +++ /dev/null @@ -1 +0,0 @@ -CONTEXT.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 6a3f7a4..37f8839 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,17 +1,8 @@ -# AI Agents +# Zapstore — Agent Instructions -This document is the entry point for AI assistants. -All behavioral authority lives in the project spec, not here. +Local-first, Nostr-native app store for Android. -If anything in this file conflicts with files under `spec/guidelines/`, -this file is wrong. - -## What This Repository Is - -Zapstore is a local-first, Nostr-native app store for Android. -It discovers, downloads, verifies, and installs APKs signed by developers the user trusts. - -Users can support developers directly via Lightning zaps. +All behavioral authority lives in `spec/guidelines/`. If this file conflicts, guidelines win. ## Quick Reference @@ -22,51 +13,29 @@ Users can support developers directly via Lightning zaps. | Quality standards | `spec/guidelines/QUALITY_BAR.md` | | Product vision | `spec/guidelines/VISION.md` | | Feature specs | `spec/features/` | -| Active work | `work/` | +| Active work | `spec/work/` | +| Decisions & learnings | `spec/knowledge/` | -## Project Spec Structure - - spec/ - guidelines/ # Permanent rules (human-owned, never AI-modified) - ARCHITECTURE.md # Package boundaries, dependencies, key patterns - INVARIANTS.md # Non-negotiable behavioral guarantees - QUALITY_BAR.md # Standards, when to create specs - VISION.md # Product goals and non-goals - - features/ # Feature specs (behavioral contracts) - _TEMPLATE.md # Template with example - FEAT-001-*.md # Actual feature specs - - work/ # Active work packets (temporary, delete after merge) - _TEMPLATE.md # Template with example - WORK-001-*.md # Actual work packets - -## How to Work - -1. Before implementing, check `spec/features/` for a relevant feature spec -2. For non-trivial work, create a work packet in `work/` -3. Every code change must trace to a task in the work packet -4. If a spec is unclear or incorrect, report a Spec Issue—do not guess - -See `spec/guidelines/QUALITY_BAR.md` for what qualifies as "non-trivial." +Guidelines are symlinked into `.cursor/rules/` and auto-load. ## File Ownership -**Never modify** files in `spec/guidelines/`. -If a guideline seems wrong or incomplete, report it as a Spec Issue. - | Path | Owner | AI May Modify | |------|-------|---------------| | `spec/guidelines/*` | Human | No | -| `spec/features/*` | Human | No (unless explicitly asked) | -| `work/*.md` | AI | Yes | -| `lib/**` | Shared | Yes | -| `test/**` | Shared | Yes | -| `AGENTS.md` | Human | No | +| `spec/features/*` | Human | No (unless asked) | +| `spec/work/*.md` | AI | Yes | +| `spec/knowledge/*.md` | AI | Yes | +| `lib/**`, `test/**` | Shared | Yes | -## Working Rules +## Key Commands + +```bash +fvm flutter pub get # Dependencies +fvm flutter analyze # Lint +fvm flutter test # Tests +``` + +## Project Rules -- Prefer small, localized changes. Avoid unrelated refactors. -- After dependency changes, run: `fvm flutter pub get` -- Fix any analyze/lint errors introduced by your changes. - Assume Android as default target unless instructed otherwise. diff --git a/spec/guidelines/ARCHITECTURE.md b/spec/guidelines/ARCHITECTURE.md index 031bebe..8b5cd4f 100644 --- a/spec/guidelines/ARCHITECTURE.md +++ b/spec/guidelines/ARCHITECTURE.md @@ -1,3 +1,8 @@ +--- +description: Architecture — layers, dependency rules, ownership, common Dart/Flutter patterns +alwaysApply: true +--- + # Zapstore — Architecture ## Core Principle diff --git a/spec/guidelines/INVARIANTS.md b/spec/guidelines/INVARIANTS.md index acd428d..85d9d47 100644 --- a/spec/guidelines/INVARIANTS.md +++ b/spec/guidelines/INVARIANTS.md @@ -1,3 +1,8 @@ +--- +description: Non-negotiable invariants — UI safety, async discipline, local-first, security, lifecycle +alwaysApply: true +--- + # Zapstore — Invariants The following guarantees are non-negotiable. diff --git a/spec/guidelines/QUALITY_BAR.md b/spec/guidelines/QUALITY_BAR.md index f58471d..5a6f291 100644 --- a/spec/guidelines/QUALITY_BAR.md +++ b/spec/guidelines/QUALITY_BAR.md @@ -1,3 +1,8 @@ +--- +description: Quality expectations — when to spec, layer standards, testing, anti-patterns, AI workflow +alwaysApply: true +--- + # Zapstore — Quality Bar ## General Expectations @@ -66,6 +71,20 @@ If multiple phases: `WORK-005-a.md`, `WORK-005-b.md` (same feature number). - Failure, cancellation, and degraded-network paths must be covered. - Tests that only assert the happy path are insufficient. +### Test layers + +- **Widget tests** (`flutter test`): preferred for UI state machines, all states + (loading, empty, error, retry). Fully headless, no device needed. +- **Integration tests** (`flutter test integration_test/`): used for flows that + cross platform boundaries — downloads, installs, storage, notifications. + Run against a connected Android emulator or device via ADB. +- **Patrol** is the integration test framework. Use `patrolTest` and `$.native` + for interacting with Android system UI (e.g. PackageInstaller dialogs). + Do not use `testWidgets` for integration tests. +- Pre-grant `REQUEST_INSTALL_PACKAGES` via + `adb shell appops set REQUEST_INSTALL_PACKAGES allow` + before running install tests, to avoid a permissions setup screen. + ## Anti-Patterns - Silent failures @@ -91,7 +110,7 @@ This project uses a spec-first workflow to collaborate safely with AI. ### What AI Owns -- Work packets under `work/` +- Work packets under `spec/work/` - Refinement of task plans during implementation ### Spec-First Rule @@ -109,3 +128,7 @@ For non-trivial work, changes are not complete unless: - Edge cases and failure modes are addressed This workflow exists to prevent AI drift, accidental refactors, and UX regressions. + +## Knowledge Entries + +After a work packet merges, promote non-obvious decisions to `spec/knowledge/DEC-XXX-*.md`. See `spec/knowledge/_TEMPLATE.md` for format and criteria. diff --git a/spec/guidelines/VISION.md b/spec/guidelines/VISION.md index ad04977..d2eeec7 100644 --- a/spec/guidelines/VISION.md +++ b/spec/guidelines/VISION.md @@ -1,3 +1,8 @@ +--- +description: Product vision — what Zapstore is, who it serves, what success means +alwaysApply: true +--- + # Zapstore — Vision ## What Zapstore Is diff --git a/spec/knowledge/_TEMPLATE.md b/spec/knowledge/_TEMPLATE.md new file mode 100644 index 0000000..5c4092f --- /dev/null +++ b/spec/knowledge/_TEMPLATE.md @@ -0,0 +1,74 @@ +--- +date: YYYY-MM-DD +tags: [tag1, tag2, tag3] +problem: One-line description of the problem encountered +--- + +# DEC-XXX — Short Title + +## Problem + +What went wrong, or what was unclear. Concrete and specific. + +## Context + +Why this came up. What was being built. What made it non-obvious. + +## Decision + +What was chosen. One clear statement. + +## Options Considered + +- **Option A** — description, why rejected +- **Option B** — description, why rejected +- **Option C (chosen)** — description, why selected + +## Rationale + +Why this option fits this codebase, this team, this context. + +## How to Avoid This Problem Next Time + +Concrete rule or pattern an agent can follow automatically: +- Do X instead of Y +- When you see Z, always do W +- Reference: `path/to/example.go` shows the correct pattern + +--- + +# Example: DEC-001 — Separate polling from local watching + +--- +date: 2026-02-04 +tags: [state-management, providers, riverpod, skeleton] +problem: Mixed local/remote data in one provider caused skeleton showing during refreshes +--- + +## Problem + +The updates screen showed a skeleton loading state during every pull-to-refresh, even when data was already loaded locally. + +## Context + +Building the updates screen. A single `CategorizedUpdatesNotifier` was both watching local DB and performing remote fetches. Timer-based invalidation triggered network requests on every rebuild. + +## Decision + +Split into two providers: one owns remote polling, one watches local data only. + +## Options Considered + +- **Single provider** — simpler, but mixing concerns caused skeleton/loading state confusion +- **Split providers (chosen)** — `UpdatePollerNotifier` (remote) + `CategorizedUpdatesNotifier` (local only) + +## Rationale + +Local provider is purely reactive — it never triggers network. Remote provider handles network + throttling. Clear separation means skeleton logic is simple: show only when no local data matches. + +## How to Avoid This Problem Next Time + +- Never mix `RemoteSource` and `LocalSource` queries in the same provider +- If a provider needs both local reactivity and remote fetching, split it +- Skeleton rule: `showSkeleton = installedIds.isNotEmpty && !hasAnyMatch` — once any match exists, never show skeleton again +- See `lib/services/updates_service.dart` for the correct pattern diff --git a/work/WORK-001-package-manager.md b/spec/work/WORK-001-package-manager.md similarity index 100% rename from work/WORK-001-package-manager.md rename to spec/work/WORK-001-package-manager.md diff --git a/work/WORK-002-batch-progress.md b/spec/work/WORK-002-batch-progress.md similarity index 100% rename from work/WORK-002-batch-progress.md rename to spec/work/WORK-002-batch-progress.md diff --git a/work/WORK-004-background-notifications.md b/spec/work/WORK-004-background-notifications.md similarity index 100% rename from work/WORK-004-background-notifications.md rename to spec/work/WORK-004-background-notifications.md diff --git a/work/WORK-005-updates-screen.md b/spec/work/WORK-005-updates-screen.md similarity index 100% rename from work/WORK-005-updates-screen.md rename to spec/work/WORK-005-updates-screen.md diff --git a/work/_TEMPLATE.md b/spec/work/_TEMPLATE.md similarity index 94% rename from work/_TEMPLATE.md rename to spec/work/_TEMPLATE.md index 1c870b4..73cc9f7 100644 --- a/work/_TEMPLATE.md +++ b/spec/work/_TEMPLATE.md @@ -40,6 +40,10 @@ Report blockers here instead of guessing. Format: Brief updates as work proceeds. +## On Merge + +Delete this work packet. If any decision here is non-obvious and worth remembering, promote it to `spec/knowledge/DEC-XXX-short-title.md` first. + --- # Example: WORK-002 — NWC Zaps