diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..24b4ff5 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "expo@expo-plugins": true + } +} diff --git a/.claude/skills/10x-rule-review/SKILL.md b/.claude/skills/10x-rule-review/SKILL.md new file mode 100644 index 0000000..a4707c8 --- /dev/null +++ b/.claude/skills/10x-rule-review/SKILL.md @@ -0,0 +1,313 @@ +--- +name: 10x-rule-review +description: > + Review the condition of an "AI rules" file (CLAUDE.md, AGENTS.md, + .cursor/rules/*.mdc, .github/copilot-instructions.md, .windsurfrules, + nested per-area rule files, or any other rule-for-AI markdown) and produce + a 5-point scorecard with concrete, actionable fixes. Use when the user + invokes /10x-rule-review with a path to a rules file, or asks to "review + AI rules", "audit AGENTS.md", "check my CLAUDE.md", "score my agent + instructions", "is this rules file healthy", or similar. The skill is + agnostic to which tool the rules file targets — it scores the file as a + rule-for-AI artifact, not as a project document. +allowed-tools: + - Read + - Glob + - Grep + - Edit + - AskUserQuestion +--- + +# 10x Rule Review + +Score an AI rules file on five axes and return concrete fixes. The file under review is whatever rule-for-AI markdown the user passes in — this skill does not assume CLAUDE.md, AGENTS.md, or any specific tool. + +The skill never edits the file. It produces a scorecard. The user decides what to act on. + +## Input resolution + +`$ARGUMENTS` should be a path to a single markdown file (absolute, repo-relative, or `@`-prefixed). Examples: + +- `@CLAUDE.md` +- `AGENTS.md` +- `.cursor/rules/api.mdc` +- `src/api/AGENTS.md` +- `.github/copilot-instructions.md` +- `~/.claude/CLAUDE.md` + +If `$ARGUMENTS` is empty, ask the user once for the path. Do not guess. + +If the path resolves to a directory, ask which file to review. If it resolves to multiple files (e.g. `**/AGENTS.md`), score them one at a time and report each scorecard separately — do not merge. + +If the file does not exist, stop and report the path. Do not invent content. + +## What this skill does NOT do + +- Does not edit the rules file *unless the user explicitly approves the reorder proposed by Check 5*. The default output is read-only. +- Does not generate a full "fixed version" of the file. At most, Check 5 may move/regroup sections; it never rewrites rule content. +- Does not assume the file's tool target. CLAUDE.md, AGENTS.md, `.mdc`, `.windsurfrules`, custom names — all treated as "a rules-for-AI file". +- Does not score *project content* (architecture, tech choices, conventions). It scores the *rule artifact's condition* — the same way a code review scores code, not the product. + +## Procedure + +1. Read the file in full (use `Read` once; if it's > 2000 lines, read in chunks until complete). +2. Compute Checks 1–4. +3. Run Check 5 in its own multi-step flow (5a list → 5b comment → 5c propose → 5d ask via `AskUserQuestion` → 5e atomic-change reminder). The reorder edit, if any, happens here and only with explicit user approval. +4. Print the scorecard in the exact format under "Output format". Include the reorder-proposal summary and the user's decision in the Check 5 findings. +5. Stop. Do not propose further follow-up actions unless the user asks. + +--- + +## The 5 checks + +### Check 1 — Length + +Count non-empty lines (ignore blank lines and pure separator lines like `---`). + +| Lines | Verdict | Symbol | +|-------------|--------------|--------| +| 0–200 | fine | OK | +| 201–500 | watch out | WARN | +| 501+ | warn | FAIL | + +Why it matters: long rule files crowd out the user's prompt in the context window, and middle-of-file rules get the weakest attention from the model. Length is a proxy for "you're paying context for things the agent doesn't need every session." + +For WARN/FAIL, suggest: +- Split per-area rules into nested files closer to their code (e.g. `src/api/AGENTS.md`). +- Replace duplicated docs with `@`-references to the canonical file. +- Drop rules that aren't tied to a recurring agent failure mode. + +### Check 2 — Direct code/config snippets + +Scan for fenced code blocks (```` ``` ````) and inline code blocks longer than ~3 lines. + +Flag any block that looks like: +- An example component, endpoint, migration, schema, query, bash script or test. +- A configuration file (`tsconfig.json`, `eslintrc`, `package.json`, `wrangler.toml`). +- A migration template or boilerplate that lives elsewhere in the repo. + +Do **not** flag: +- Short structural snippets used to define a *format* the agent must produce (e.g. a 2–4 line error-shape template). +- Command examples (`npm run dev`, `git rebase`, etc.). +- Mermaid/diagram blocks. + +For each flagged block, suggest: +- Move the snippet to a real file in the repo. +- Replace the block with a one-line `@`-reference, e.g. `@src/features/users/user.service.ts`, `@docs/api-errors.md`. +- Reason: the example will be wrong in two places at the next refactor; a reference can't drift. + +Verdict: OK if 0 flagged blocks · WARN if 1–2 · FAIL if 3+. + +### Check 3 — Precise language + +Scan for vague intent that cannot be checked against a diff. Common offenders: + +- "Write clean code" +- "Follow best practices" +- "Care about quality" +- "Be consistent" +- "Use modern patterns" +- "Make it readable / maintainable / robust" +- "Handle errors properly" +- "Keep things simple" + +For every match, **always propose at least one concrete, testable alternative grounded in this project's context**. Never suggest "just delete it" — the author put the line there for a reason; your job is to translate the intent into something a reviewer can check against a diff. + +To ground the suggestion, pull signal from: +- the file under review (stack mentioned, naming conventions stated elsewhere, hard rules in other sections), +- nearby paragraphs around the vague phrase (what was the author about to say?), +- visible repo context if available (`package.json`, `tsconfig.json`, framework choice, lint config, sibling rule files). + +If the project context truly doesn't suggest anything specific, propose a sensible default for the detected stack and label it **(assumed)** so the author knows to confirm. + +Examples (note how each replacement borrows project-specific names/conventions, not generic advice): + +| Vague phrase in file | Project context signal | Grounded testable replacement | +|-----------------------------------|--------------------------------------------------|------------------------------------------------------------------------------------------------------------| +| "Write clean code" | TypeScript + ESLint mentioned in same file | "Avoid `any`. Functions over 40 lines must be split. Run `pnpm lint` before committing." | +| "Handle errors properly" | Hard rule earlier: API returns `{ error: {...} }` shape | "API handlers must return `{ error: { code, message, context } }` per the shape defined above. Never throw raw." | +| "Be consistent with naming" | File mentions `feature.handler.ts` elsewhere | "Use `.handler.ts` (matching the existing handlers in `src/api/`), not `featureHandler.ts`." | +| "Use modern patterns" | Project uses native JS, no lodash in `package.json` | "Use native `Array`/`Object` methods. Do not add `lodash` — it's not in `package.json` and we keep it that way." | +| "Make components readable" | React + Tailwind project | "Components over 150 lines must be split. Tailwind classes go through `cn()` for conditionals (assumed — confirm if a different helper is used)." | +| "Keep things simple" | Python FastAPI service | "Prefer one Pydantic model per request/response. No nested decorators beyond `@router.post` + `@requires_auth`." | + +Verdict: OK if 0 vague phrases · WARN if 1–3 · FAIL if 4+. + +Verdict: OK if 0 vague phrases · WARN if 1–3 · FAIL if 4+. + +### Check 4 — Redundant knowledge + +You are the actor agent reviewing this file. Read it the way you'd read it at the start of a session and ask one question after each paragraph: + +> **"Did I already know this before I opened the file?"** + +If the answer is "yes, this is in my training data" or "yes, this is the framework's documented default" or "yes, the README/lint config already says this" — flag it. The author paid context for something you didn't need explained. + +Use these self-checks while scanning: + +- **The "no surprise" test.** Could you have produced this paragraph yourself if asked, with no project access? If yes — redundant. +- **The "framework default" test.** Is the rule restating something the framework, the lint config, the type checker, or the test runner already enforces (e.g. "use TypeScript strict mode", "use `useEffect` cleanup", "FastAPI uses Pydantic for validation", "PostgreSQL supports JSONB")? If yes — redundant. The tool will catch the violation; the prose won't add anything. +- **The "definition" test.** Does the paragraph define a generic engineering term ("what is a service layer", "what REST is", "what hooks are", "what JSX is", "what is `Decimal`")? You know these. Flag and delete. +- **The "could be a link" test.** Does it duplicate `README.md`, `package.json` scripts, the project layout, or `.eslintrc` settings? If yes — replace with `@README.md` / `@package.json` / `@.eslintrc.json`. A reference doesn't drift; copied prose does. +- **The "tutorial smell" test.** If the paragraph reads like a section from the framework's "Getting Started" page or a Medium article — it's tutorial content, not project knowledge. You read those during training. + +What is **not** redundant (don't flag): +- Project-specific conventions that contradict the framework default ("we use `useEffect` only for non-data side effects"). +- Local pitfalls and historical workarounds you couldn't infer from the code ("the `events` table is partitioned by month — bulk inserts to the wrong partition fail silently"). +- Internal naming, layout, or workflow rules ("postings live in `_.posting.ts`"). +- Rules that look generic but are tied to a real incident (the file should mention the incident or link to a failure-modes register). + +For each flagged paragraph, suggest one of: +- **Delete it** — you already knew it. +- **Replace with `@`-reference** — `@README.md`, `@tsconfig.json`, `@docs/...`. +- **Keep only if backed by an incident** — and if so, ask the author to add the incident note inline so the rule survives future audits. + +Verdict: OK if 0 redundant paragraphs · WARN if 1–3 · FAIL if 4+. + +### Check 5 — Rule ordering + +Models pay more attention to the start and end of long contexts ("U-shaped attention"). Critical rules buried in the middle of a long file are statistically less likely to be followed. This check has its own multi-step flow because reordering a file is a meaningful edit, not a one-line fix. + +Run the steps in order. The result of this check goes into the scorecard *and* may trigger an interactive reorder. + +#### Step 5a — List the current high-level order + +Walk the file and print the current top-level structure as a numbered list. Use H1/H2 headings (and H3s only if there are no H2s). Include the line number of each heading. Do **not** comment yet — just lay out what's there. + +Example: +``` +Current order: +1. # Welcome to OrderFlow (line 1) +2. ## About the team (line 5) +3. ## Project mission (line 9) +4. ## Our values (line 13) +5. ## Tech stack (line 22) +6. ## Setup (line 36) +7. ## TypeScript (line 78) +... +N. ## Project conventions (line 312) +``` + +If the file has no headings, say so explicitly: *"No section headings — file is one undifferentiated block."* + +#### Step 5b — Comment on the order + +Now annotate the list. For each section, give it a short tag and a one-line note. Use these tags: + +- **CRITICAL** — load-bearing rule (security, money, irreversibility, project-specific "never do X"). +- **USEFUL** — real project knowledge that helps but isn't a tripwire. +- **INTRO** — welcome/mission/team — lowers signal density at the top. +- **REDUNDANT** — already flagged in Check 4 (framework defaults, definitions, tutorial content). +- **VAGUE** — already flagged in Check 3. +- **REFERENCE** — points to other files via `@`-syntax (cheap, fine anywhere). + +Then state the structural problem in one paragraph. Examples: + +> "Critical security and tenancy rules are at the bottom (line 312). The first 35 lines are INTRO/values/marketing, which the model will weight heavily but which contain no actionable rules. Risk: the agent reads the bloat fully and skims past the rules that actually matter." + +> "Order is roughly correct — hard rules at top, conventions in the middle, references at the bottom. One INTRO paragraph at line 1 could be tightened, but no structural reshuffle needed." + +#### Step 5c — Propose a better order (only if needed) + +If the comment in 5b identified a real problem, propose a target order. Frame it as *"sections moved to top / kept / moved to bottom / removed"*, not as a full rewrite of every line. + +Example: +``` +Proposed order: +1. ## Hard rules (was: line 312) ← moved to top +2. ## Project conventions (was: line 312, split) ← moved up +3. ## Tech stack (was: line 22) ← kept +4. ## Setup (was: line 36) ← kept, replace with @README.md if possible +5. ## Failure modes (new section) ← collect incident-driven rules here +— ## About the team / Mission / Values ← remove (Check 3/4 already flagged these) +``` + +If 5b found no problem, skip 5c entirely — say *"Order is sound; no reshuffle needed."* + +#### Step 5d — Ask before reordering + +If 5c produced a proposal, **ask the user via `AskUserQuestion`** before touching the file. Phrase the question concretely. Example options: + +- **Yes, reorder the file now** — apply the proposed structure, preserve all rule content, only move/regroup sections. +- **Only move the critical rules to the top** — minimal change: lift hard rules to the top, leave the rest alone. +- **No, just leave the suggestion in the report** — don't edit the file; the scorecard stands. +- **Show me the diff first** — produce the reordered file as a preview block in chat, no write. + +If the user picks an editing option, apply it with care: preserve every byte of rule content (only headings and section blocks move), and write a single edit. If the user picks "leave the suggestion", do nothing. + +#### Step 5e — Atomic-change reminder + +Always end Check 5 with this reminder, regardless of whether a reorder happened: + +> **Test each change in your next agent session.** Reordering a rules file is a context-shape change — its effect on agent behavior only shows up the next time you run a real task. Apply changes one at a time (atomic): reorder, then run a representative task, then move on to the next change (split, dedupe, rewrite). Bundling multiple structural changes makes it impossible to attribute a behavior shift to a specific edit. + +#### Verdict + +Score the file before any reorder happens, based on the original order: + +- **OK** — top of file is dense with CRITICAL/USEFUL rules, clear headings, no INTRO bloat at the start. +- **WARN** — structure is mixed: some critical rules at top, others buried; or non-trivial INTRO at the start. +- **FAIL** — critical rules appear after line 200, or the file has no headings at all, or the top 30+ lines are pure INTRO/marketing. + +--- + +## Output format + +Print exactly this, in this order. Use Polish or English matching the user's prompt language. Reference `path:line` for every concrete finding so the user can jump straight to it. + +``` +# Rule Review — + +**Overall:** + +## Scorecard + +| # | Check | Verdict | Score | +|---|----------------------|---------|-------| +| 1 | Length | OK/WARN/FAIL | non-blank lines | +| 2 | Direct snippets | OK/WARN/FAIL | flagged blocks | +| 3 | Precise language | OK/WARN/FAIL | vague phrases | +| 4 | Redundant knowledge | OK/WARN/FAIL | redundant rules | +| 5 | Rule ordering | OK/WARN/FAIL | | + +## Findings + +### 1. Length — +- non-blank lines. +- + +### 2. Direct snippets — +- `path:line-range` — → suggest `@` reference. +- ... + +### 3. Precise language — +- `path:line` — "" → "" +- ... + +### 4. Redundant knowledge — +- `path:line` — +- ... + +### 5. Rule ordering — +- +- + +## Top 3 actions +1. +2. +3. +``` + +If a check is OK, still list it in the table but skip the "Findings" subsection (write `### N. — OK` and one short line, nothing more). + +The "Top 3 actions" must be ordered by leverage, not by check number. Pick from across all five checks. + +--- + +## Edge cases + +- **File under 50 lines:** still run all five checks. Short files often fail Check 3 (vague) and Check 4 (redundant) the most. +- **File is mostly references (`@…`) and few inline rules:** that's a good sign for Checks 2 and 4. Don't penalize it. +- **File is a `.mdc` with frontmatter (`globs:`, `alwaysApply:`):** count rule lines from after the frontmatter. The frontmatter itself is configuration, not rule content. +- **File is a generated stub from `/init` and untouched:** still review it. Often Check 4 (redundant) will dominate — that's the signal to clean it. +- **Multiple rule files in the project:** review the one passed in. Mention sibling files in "Top 3 actions" only if relevant (e.g. duplication between root `AGENTS.md` and a nested one). diff --git a/AGENTS.md b/AGENTS.md index b980aaa..1b4c6c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,125 +1,133 @@ # AI Agents Instructions (simple-notepad) ## Mission -This repo is a small Expo + React Native app (using Expo Router and NativeWind/Tailwind) for creating, listing, and editing “notes” and “checklist lists”, persisted in a local SQLite database. + +This repo is a small Expo + React Native app (using Expo Router and NativeWind/Tailwind) for creating, listing, and editing "notes" and "checklist lists", persisted in a local SQLite database. When you (or another AI agent) are asked to implement a change, prefer working through the existing route/components structure and the centralized SQLite data layer in `lib/dataStorage.ts`. -## Tech Stack (what to assume) -- **Runtime**: React Native + Expo -- **Routing**: **Expo Router** (file-based routes under `app/`) -- **Styling**: NativeWind + Tailwind (`global.css`, `tailwind.config.js`, Tailwind classes in JSX) -- **Storage**: `expo-sqlite` with migrations handled in `app/_layout.tsx` via `migrateDbIfNeeded` -- **TypeScript**: `strict: true` +## Quick Safety Checklist (do not break invariants) -## Repo Layout (where things live) -- `app/`: screens/routes (Expo Router) - - Example routes: - - `app/index.tsx`: notes list screen - - `app/add-note.tsx`: create note - - `app/edit-note/[id].tsx`: edit note by numeric id - - `app/note/[id].tsx`: view note by numeric id - - `app/add-list.tsx`: create list - - `app/edit-list/[id].tsx`: edit list by numeric id - - `app/list/[id].tsx`: view list by numeric id -- `components/`: reusable UI pieces (buttons, inputs, forms, etc.) - - `components/NoteForm.tsx`: shared note create/edit form - - `components/AddContentDropdown.tsx`: “add note or list” UI - - `components/state/*`: loading/not-found UI -- `lib/`: non-UI logic - - `lib/dataStorage.ts`: SQLite schema, migrations, and CRUD helpers - - `lib/theme.ts`: navigation theme colors - - `lib/utils.ts`: `cn()` utility for className merging -- `hooks/`: small hooks used by screens - - `useParsedNumericRouteParam`: parses numeric `[id]` params safely - - `useHardwareBackHandler`: handles Android back navigation +- Do not remove/skip the `migrateDbIfNeeded` hook from `SQLiteProvider` in `app/_layout.tsx`. +- Keep `NOTE_TYPE`/`LIST_TYPE` semantics consistent with `getListItemsById` and list update queries. +- Avoid direct SQL edits outside `lib/dataStorage.ts`. +- Do not change `journal_mode` away from `DELETE` — the Android widget requires it (see `NATIVE_CHANGES.md`). +- Do not run `expo prebuild --clean` — it will wipe the widget's native files. -## Running the app (for humans/agents) -Common scripts from `package.json`: -- Dev server (all platforms): `npm run dev` (runs `expo start -c`) -- Platform-specific: - - `npm run ios` (simulator) - - `npm run android` - - `npm run web` -- Clean: `npm run clean` (removes `.expo` and `node_modules`) +## Native Code -## Routing / Screen patterns (Expo Router) -- Routes are defined by filenames under `app/`. -- Dynamic numeric route params use: - - `useParsedNumericRouteParam('id')` - - screens then guard with `isValidId` (invalid id -> not found / redirect behavior) -- For typed routing (`expo` config has `experiments.typedRoutes: true`): - - when passing dynamic routes to `router.push`, existing code uses casts like `as never`. +The `android/` folder contains manual modifications on top of `expo prebuild` output — primarily an Android home-screen widget. + +**See [`NATIVE_CHANGES.md`](./NATIVE_CHANGES.md) for the full list of native files, their purpose, AndroidManifest entries, schema dependencies, and upgrade notes.** + +Key points for agents: + +- Do **not** run `expo prebuild --clean` — use `expo prebuild` (no `--clean`) to preserve widget files. +- After any write operation in `lib/dataStorage.ts`, call `syncAndroidNoteListWidgetFromApp()` (already defined there) so the widget refreshes. All existing CRUD helpers already do this. + +## When implementing a feature (agent playbook) + +1. **Locate the route** to change/add under `app/`. +2. If the feature needs persistence, identify/extend the correct helper(s) in `lib/dataStorage.ts`. +3. For DB changes: + - update the schema migration in `migrateDbIfNeeded` + - bump `DATABASE_VERSION` + - keep existing migrations compatible (older installs should migrate forward) +4. For UI: + - reuse existing components in `components/` (especially `NoteForm`, `ListForm`, and `components/ui/*`) + - use Tailwind/NW class names (via `className`) +5. Match existing loading/not-found patterns for numeric params and record-type checks. +6. If adding a new write operation in `lib/dataStorage.ts`, call `syncAndroidNoteListWidgetFromApp()` after the DB write (see existing helpers for the pattern). ## SQLite Database Model (most important invariants) + ### Where the DB is initialized + `app/_layout.tsx` wraps the router with: + - `` So the migration function in `lib/dataStorage.ts` is responsible for keeping schema compatible across app updates. ### Schema and versioning -In `lib/dataStorage.ts`: -- `DATABASE_VERSION = 2` -- `content` table columns: - - `id INTEGER PRIMARY KEY NOT NULL` - - `title TEXT NOT NULL` - - `note TEXT NOT NULL` - - `type INTEGER NOT NULL` (added in migration; semantic types below) -- `type` constants: - - `NOTE_TYPE = 0` - - `LIST_TYPE = 1` -- Migration approach: - - Reads `PRAGMA user_version` - - If `user_version >= DATABASE_VERSION`: do nothing - - If missing/old: - - (Re)creates the base table if `user_version === 0` - - Adds the `type` column if it’s missing; assigns default `NOTE_TYPE` and fixes nulls - - Sets `PRAGMA user_version = DATABASE_VERSION` + +@lib/dataStorage.ts ### How list content is stored + Lists store their items inside the `note` column as a JSON string: + - `stringifyListItems(items)` stores `{ checked, text }[]` - `parseListItems(rawContent)` validates/filters parsed items CRUD helpers to use: + - Notes: - `addNote`, `getNoteById`, `updateNote`, `deleteNote` - Lists: - `addList`, `getListItemsById`, `updateList`, `updateListItems` -Important rule: updating list items uses `UPDATE content SET note = ? WHERE id = ? AND type = ?` (ensures you don’t overwrite a note’s data by accident). +Important rule: updating list items uses `UPDATE content SET note = ? WHERE id = ? AND type = ?` (ensures you don't overwrite a note's data by accident). + +## Repo Layout (where things live) + +- `app/`: screens/routes (Expo Router) + - `app/index.tsx`: notes list screen + - `app/add-note.tsx`: create note + - `app/edit-note/[id].tsx`: edit note by numeric id + - `app/note/[id].tsx`: view note by numeric id + - `app/add-list.tsx`: create list + - `app/edit-list/[id].tsx`: edit list by numeric id + - `app/list/[id].tsx`: view list by numeric id +- `components/`: reusable UI pieces + - `components/NoteForm.tsx`: shared note create/edit form + - `components/ListForm.tsx`: shared list create/edit form + - `components/AddContentDropdown.tsx`: "add note or list" UI + - `components/navigation/HeaderBackButton.tsx`: back arrow button for screen headers + - `components/state/`: `ScreenLoadingState` and `ScreenNotFoundState` + - `components/ui/`: primitive UI components — `button`, `card`, `icon`, `input`, `textarea`, `text` +- `lib/`: non-UI logic + - `lib/dataStorage.ts`: SQLite schema, migrations, and CRUD helpers + - `lib/theme.ts`: navigation theme colors + - `lib/utils.ts`: `cn()` utility for className merging +- `hooks/`: small hooks used by screens + - `useParsedNumericRouteParam`: parses numeric `[id]` params safely + - `useHardwareBackHandler`: handles Android back navigation + - `useKeyboardOffset`: tracks keyboard visibility on Android to compute bottom padding + +## Tech Stack (what to assume) + +Expo ~54, Expo Router, NativeWind v4, expo-sqlite, TypeScript strict — see @package.json for exact versions. + +## Routing / Screen patterns (Expo Router) + +- Dynamic numeric route params use: + - `useParsedNumericRouteParam('id')` + - screens then guard with `isValidId` (invalid id -> not found / redirect behavior) +- For typed routing (`expo` config has `experiments.typedRoutes: true`): + - when passing dynamic routes to `router.push`, existing code uses casts like `as never`. ## UI + UX conventions + - Note create/edit uses `components/NoteForm.tsx`, which calls `onSave(trimmedTitle, noteContent.trim())`. +- List create/edit uses `components/ListForm.tsx`. - Screens typically return: - `ScreenLoadingState` while fetching - - `ScreenNotFoundState` when `id` is invalid or the record type doesn’t match the expected screen + - `ScreenNotFoundState` when `id` is invalid or the record type doesn't match the expected screen - Hardware back navigation: - - screens use `useHardwareBackHandler(() => router.replace('/'))` or redirect to a “backTarget”. + - screens use `useHardwareBackHandler(() => router.replace('/'))` or redirect to a "backTarget". +- Use `useKeyboardOffset` for bottom padding on screens with inputs (handles Android accessory bar). +- Use `HeaderBackButton` from `components/navigation/HeaderBackButton.tsx` for screen header back arrows. ## Code Style / Quality Bar -- TypeScript `strict` mode is on, so be careful with `null`, `'loading'`, and route params. + +- Use type narrowing (not !) for nullable fields and 'loading' states. Always unwrap numeric route params via useParsedNumericRouteParam — never cast useLocalSearchParams() output directly. - Follow Prettier config expectations: - single quotes, `printWidth: 100`, Tailwind plugin support. -- Prefer adding logic to `lib/dataStorage.ts` instead of scattering SQL strings in screens. +- All SQL lives in lib/dataStorage.ts. No exceptions. -## When implementing a feature (agent playbook) -1. **Locate the route** to change/add under `app/`. -2. If the feature needs persistence, identify/extend the correct helper(s) in `lib/dataStorage.ts`. -3. For DB changes: - - update the schema migration in `migrateDbIfNeeded` - - bump `DATABASE_VERSION` - - keep existing migrations compatible (older installs should migrate forward) -4. For UI: - - reuse existing components in `components/` (especially `NoteForm` and `components/ui/*`) - - use Tailwind/NW class names (via `className`) -5. Match existing loading/not-found patterns for numeric params and record-type checks. - -## Quick Safety Checklist (do not break invariants) -- Do not remove/skip the `migrateDbIfNeeded` hook from `SQLiteProvider` in `app/_layout.tsx`. -- Keep `NOTE_TYPE`/`LIST_TYPE` semantics consistent with `getListItemsById` and list update queries. -- Avoid direct SQL edits outside `lib/dataStorage.tsx`. +## Running the app (for humans/agents) -Last scanned: 2026-04-29 +@README.md +Last scanned: 2026-05-18 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b89c64d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +See [AGENTS.md](./AGENTS.md) for project instructions. diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt index 679e26e..2525569 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt @@ -2,6 +2,9 @@ package com.pgarr.simplenotepad.widget import android.content.Context import android.content.Intent +import android.text.SpannableString +import android.text.Spanned +import android.text.style.StrikethroughSpan import android.widget.RemoteViews import android.widget.RemoteViewsService import com.pgarr.simplenotepad.R @@ -39,18 +42,24 @@ class NoteListRemoteViewsFactory( val item = items[position] val rv = RemoteViews(context.packageName, R.layout.widget_list_item) - // Set text, with strikethrough if checked - rv.setTextViewText(R.id.item_text, item.text) + // Set text with strikethrough for checked items (matches app line-through style) + if (item.checked) { + val spannable = SpannableString(item.text) + spannable.setSpan(StrikethroughSpan(), 0, item.text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + rv.setTextViewText(R.id.item_text, spannable) + } else { + rv.setTextViewText(R.id.item_text, item.text) + } - // Swap checkbox icon based on checked state + // Swap checkbox icon based on checked state (custom lucide-style drawables) val checkboxDrawable = if (item.checked) - android.R.drawable.checkbox_on_background + R.drawable.widget_checkbox_on else - android.R.drawable.checkbox_off_background + R.drawable.widget_checkbox_off rv.setImageViewResource(R.id.item_checkbox, checkboxDrawable) - // Dim completed items - rv.setFloat(R.id.item_text, "setAlpha", if (item.checked) 0.4f else 1.0f) + // Dim checked item text to match app muted-foreground (text-muted-foreground) + rv.setFloat(R.id.item_text, "setAlpha", if (item.checked) 0.5f else 1.0f) // Fill-in intent carries position and listId to the broadcast receiver val fillIntent = Intent().apply { diff --git a/android/app/src/main/res/drawable/widget_background.xml b/android/app/src/main/res/drawable/widget_background.xml index 630a8ab..498a31d 100644 --- a/android/app/src/main/res/drawable/widget_background.xml +++ b/android/app/src/main/res/drawable/widget_background.xml @@ -3,10 +3,6 @@ android:shape="rectangle"> - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/widget_checkbox_off.xml b/android/app/src/main/res/drawable/widget_checkbox_off.xml new file mode 100644 index 0000000..273e5a0 --- /dev/null +++ b/android/app/src/main/res/drawable/widget_checkbox_off.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/drawable/widget_checkbox_on.xml b/android/app/src/main/res/drawable/widget_checkbox_on.xml new file mode 100644 index 0000000..9aed335 --- /dev/null +++ b/android/app/src/main/res/drawable/widget_checkbox_on.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/widget_item_background.xml b/android/app/src/main/res/drawable/widget_item_background.xml new file mode 100644 index 0000000..8f70bac --- /dev/null +++ b/android/app/src/main/res/drawable/widget_item_background.xml @@ -0,0 +1,5 @@ + + + + diff --git a/android/app/src/main/res/layout/widget_list_item.xml b/android/app/src/main/res/layout/widget_list_item.xml index b4954ac..f68fee5 100644 --- a/android/app/src/main/res/layout/widget_list_item.xml +++ b/android/app/src/main/res/layout/widget_list_item.xml @@ -1,19 +1,24 @@ + + android:paddingStart="12dp" + android:paddingEnd="12dp" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:layout_marginBottom="6dp" + android:background="@drawable/widget_item_background"> - + - \ No newline at end of file + diff --git a/android/app/src/main/res/layout/widget_note_list.xml b/android/app/src/main/res/layout/widget_note_list.xml index e7ebd58..ad37593 100644 --- a/android/app/src/main/res/layout/widget_note_list.xml +++ b/android/app/src/main/res/layout/widget_note_list.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:padding="8dp" + android:padding="12dp" android:background="@drawable/widget_background"> + - \ No newline at end of file + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 0602cb7..805a77d 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -4,7 +4,10 @@ #023c69 #ffffff - #FFFFFF + + #33FFFFFF #E5E5E5 #0A0A0A + + #737373 \ No newline at end of file diff --git a/app.json b/app.json index da9742a..92b935a 100644 --- a/app.json +++ b/app.json @@ -2,12 +2,11 @@ "expo": { "name": "simple-notepad", "slug": "simple-notepad", - "version": "1.3.0", + "version": "1.4.0", "orientation": "portrait", "icon": "./assets/images/icon.png", "scheme": "simple-notepad", "userInterfaceStyle": "automatic", - "newArchEnabled": true, "splash": { "image": "./assets/images/splash.png", "resizeMode": "contain", @@ -25,7 +24,7 @@ "backgroundColor": "#ffffff" }, "package": "com.pgarr.simplenotepad", - "versionCode": 16 + "versionCode": 17 }, "web": { "bundler": "metro", @@ -44,7 +43,7 @@ "projectId": "9e3820b7-558b-4bd2-a1b2-e49561e741e6" } }, - "runtimeVersion": "1.3.0", + "runtimeVersion": "1.4.0", "updates": { "url": "https://u.expo.dev/9e3820b7-558b-4bd2-a1b2-e49561e741e6" } diff --git a/package-lock.json b/package-lock.json index f3878ed..078c720 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "simple-notepad", - "version": "1.3.0", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simple-notepad", - "version": "1.3.0", + "version": "1.4.0", "dependencies": { "@react-navigation/native": "^7.0.0", "@rn-primitives/portal": "~1.3.0", diff --git a/package.json b/package.json index 8ad1f41..3da96f8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "simple-notepad", "main": "expo-router/entry", - "version": "1.3.0", + "version": "1.4.0", "scripts": { "prebuild": "expo prebuild", "dev": "expo start",