Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ff2c576
fix: rename fusion to builder, fix hydration errors, update DevTools …
steve8708 Mar 26, 2026
23b4b89
feat: resizable chat sidebar, calendar stacking layout, mail refresh …
steve8708 Mar 26, 2026
eacd3f8
feat: improve chat UI and template layouts
steve8708 Mar 26, 2026
fd9db98
fix: prettier formatting in forms root.tsx
steve8708 Mar 26, 2026
4f5481d
feat: agent sidebar improvements, multi-tab chat, template updates
steve8708 Mar 26, 2026
2a1e4d6
feat: agent panel collapse, multi-tab chat, command palette, notion s…
steve8708 Mar 26, 2026
d2caf07
feat: calendar people overlay, agent panel collapse, forms sidebar up…
steve8708 Mar 26, 2026
1898370
fix: calendar text-aware overlap layout, queue composer UI, content t…
steve8708 Mar 26, 2026
1e19467
feat: stacking calendar layout, past/declined event styling, resource…
steve8708 Mar 26, 2026
927ed52
fix: Notion OAuth polling cleanup, add ship skill
steve8708 Mar 26, 2026
3088c32
feat: replace SSE with polling for serverless compatibility
steve8708 Mar 26, 2026
cd4dc11
feat: add Postgres support — database-agnostic core stores
steve8708 Mar 26, 2026
42d0168
fix: replace require() with ESM imports in db client and scripts
steve8708 Mar 27, 2026
5111d9d
fix: resume polling on HTTP errors instead of silently stopping
steve8708 Mar 27, 2026
2e1c7e5
fix: migration SQL dialect compat, resources panel folders, calendar …
steve8708 Mar 27, 2026
2a968ce
fix: lazy-load postgres and libsql drivers for edge runtime compat
steve8708 Mar 27, 2026
a61c330
fix: remove unused @neondatabase/serverless, update lockfile
steve8708 Mar 27, 2026
7b4ef34
fix: format EventDetailPopover.tsx
steve8708 Mar 27, 2026
4bbe710
fix: dynamic import postgres in db scripts for Workers compat
steve8708 Mar 27, 2026
6baaf22
fix: move postgres to optional peer dep for Workers compat
steve8708 Mar 27, 2026
51b5093
fix: dynamic import drizzle-orm/libsql for Workers compat
steve8708 Mar 27, 2026
ea238cf
chore: gitignore .claude/scheduled_tasks.lock
steve8708 Mar 27, 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
48 changes: 39 additions & 9 deletions .agents/skills/capture-learnings/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
---
name: capture-learnings
description: >-
Capture and apply accumulated knowledge in learnings.md. Use when the user
gives feedback, shares preferences, corrects a mistake, or when you discover
something worth remembering for future conversations.
Capture and apply accumulated knowledge via the Resources system. Use when the
user gives feedback, shares preferences, corrects a mistake, or when you
discover something worth remembering for future conversations.
user-invocable: false
---

# Capture Learnings

This is background knowledge, not a slash command. **Read `learnings.md` before starting significant work.** Update it when you learn something worth remembering.
This is background knowledge, not a slash command. **Read the `learnings.md` resource before starting significant work.** Update it when you learn something worth remembering.

## How to Read & Write Learnings

Learnings are stored as **resources** in the SQL database, not as files on disk.

- **Read:** `pnpm script resource-read --path learnings.md`
- **Write:** `pnpm script resource-write --path learnings.md --content "..."`
- **List all resources:** `pnpm script resource-list`

Resources can be **personal** (per-user, default) or **shared** (team-wide):
- `pnpm script resource-write --path learnings.md --scope personal --content "..."`
- `pnpm script resource-write --path team-guidelines.md --scope shared --content "..."`

## When to Capture

Expand All @@ -33,7 +45,7 @@ This is background knowledge, not a slash command. **Read `learnings.md` before

## Format

Add entries to `learnings.md` at the project root. Group by category:
Write learnings as markdown, grouped by category:

```markdown
## Preferences
Expand All @@ -55,19 +67,37 @@ Add entries to `learnings.md` at the project root. Group by category:

## Key Rules

1. **Read first, write second** — always check `learnings.md` before starting work
1. **Read first, write second** — always read the `learnings.md` resource before starting work
2. **Capture immediately** — don't wait until the end of the conversation
3. **Keep it concise** — one line per learning, grouped by category
4. **Don't duplicate** — if a learning exists, refine it rather than adding another
5. **learnings.md is gitignored** — safe for personal info, preferences, contacts
5. **Resources are SQL-backed** — safe for personal info, preferences, contacts. They persist across sessions and are not in git.

## Organizing Resources

Learnings are just one type of resource. You can create additional resources for different purposes:
- `learnings.md` — user preferences, corrections, patterns (personal)
- `contacts.md` — important contacts and relationships (personal)
- `team-guidelines.md` — shared team conventions (shared)
- `notes/meeting-2026-03-26.md` — meeting notes (personal or shared)

Use path prefixes like `notes/`, `docs/`, etc. to organize resources into virtual folders.

## Graduation

When a learning is referenced repeatedly, it may belong in AGENTS.md or a skill:
- Updating `learnings.md` is a Tier 1 modification (data — auto-apply)
- Updating the `learnings.md` resource is a Tier 1 modification (data — auto-apply)
- Updating a SKILL.md based on learnings is Tier 2 (source — verify after)

## Migration

If a `learnings.md` file exists at the project root (from before the Resources system), run:
```
pnpm script migrate-learnings
```
This imports the file contents into the `learnings.md` resource.

## Related Skills

- **self-modifying-code** — learnings.md updates are Tier 1; skill updates are Tier 2
- **self-modifying-code** — resource updates are Tier 1; skill updates are Tier 2
- **create-skill** — when a learning graduates, create a skill from it
119 changes: 52 additions & 67 deletions .agents/skills/files-as-database/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,102 +1,87 @@
---
name: files-as-database
name: storing-data
description: >-
How to choose between files and SQLite for storing application state. Use when
adding data models, deciding where to store data, or reading/writing
How to store application data in agent-native apps. All data lives in SQL.
Use when adding data models, deciding where to store data, or reading/writing
application data.
---

# Files vs SQLite — Choosing the Right Data Layer
# Storing Data — SQL is the Source of Truth

## Rule

Agent-native apps use **two data layers**: files for content and configuration, SQLite for structured application data. Choose the right one based on what you're storing.
All application data lives in **SQL** (SQLite locally, cloud database in production). The agent and UI share the same database. There is no filesystem dependency for data.

## When to Use Files
## How It Works

Store data as files in `data/` (JSON, markdown, images) when:
Agent-native apps use SQLite via Drizzle ORM + `@libsql/client`. This works locally out of the box and upgrades seamlessly to cloud databases (Turso, Neon, Supabase, D1) by setting `DATABASE_URL`. **Local and production behave identically.**

- **Content** — markdown documents, drafts, articles, slide decks
- **Settings/Configuration** — app settings, user preferences, sync config
- **Application state** — ephemeral UI state in `application-state/` (compose windows, search state)
- **Media** — images, uploads, generated assets
- **Data the agent edits directly** — the agent can read/write files on the filesystem without going through an API
### Core SQL Stores (auto-created, available in all templates)

Files are the shared interface between the AI agent and the UI. The agent reads and writes files directly. The UI reads files via API routes. SSE streams file changes back to the UI in real-time.
| Store | Purpose | Access |
| ------------------- | ---------------------------------------------------- | ------------------------------------------ |
| `application_state` | Ephemeral UI state (compose windows, navigation) | `readAppState()` / `writeAppState()` |
| `settings` | Persistent KV config (preferences, app settings) | `getSetting()` / `setSetting()` |
| `oauth_tokens` | OAuth credentials | `@agent-native/core/oauth-tokens` |
| `sessions` | Auth sessions | `@agent-native/core/server` |

### How (Files)
### Domain Data (per-template)

- Store data as JSON or markdown files in `data/` (or a project-specific subdirectory).
- API routes in `server/routes/` read files with `fs.readFile` and return them.
- The agent modifies files directly — no API calls needed from the agent side.
- `createFileWatcher("./data")` watches for changes and streams them via SSE.
- `useFileWatcher()` on the client invalidates React Query caches when files change.
Define schema with Drizzle ORM in `server/db/schema.ts`. Get a database instance with `const db = getDb()` from `server/db/index.ts`. All queries are async.

### File Organization
| Template | Tables |
| ------------ | --------------------------------------------- |
| **Mail** | emails, labels (+ Gmail API when connected) |
| **Calendar** | events, bookings |
| **Forms** | forms, responses |
| **Content** | documents |
| **Slides** | decks (JSON stored in SQL) |
| **Videos** | compositions in registry + localStorage |

| Question | Single file | Directory of files |
| ------------------------------------ | ----------------- | ---------------------------- |
| Are items independently addressable? | No — use one file | Yes — one file per item |
| Will there be >50 items? | Probably fine | Definitely split |
| Do items need individual URLs? | No | Yes |
| Do items change independently? | No | Yes — avoids write conflicts |
### Agent Access

## When to Use SQLite
The agent uses scripts to read/write the database:

Store data in SQLite (`data/app.db`) via Drizzle ORM + `@libsql/client` when:
- `pnpm script db-schema` — Show all tables, columns, types
- `pnpm script db-query --sql "SELECT * FROM forms"` — Run SELECT queries
- `pnpm script db-exec --sql "INSERT INTO ..."` — Run INSERT/UPDATE/DELETE
- App-specific scripts for domain operations

- **Structured records** — forms, bookings, submissions, compositions with relationships
- **Data that needs querying** — filtering, sorting, aggregation, joins
- **High-volume data** — hundreds or thousands of records
- **Relational data** — foreign keys, references between entities
- **Data that benefits from transactions** — atomic multi-table writes
### Cloud Deployment

### How (SQLite)
Local SQLite works out of the box. To deploy to production with a cloud database:

- Define schema with Drizzle ORM in `server/db/schema.ts`.
- Get a database instance with `const db = getDb()` from `server/db/index.ts`.
- All queries are **async** (using `@libsql/client`, not `better-sqlite3`).
- The agent uses DB scripts (`pnpm script db-schema`, `db-query`, `db-exec`) or app-specific scripts to read/write data.
- Set `DATABASE_URL` env var for cloud database (Turso); defaults to local `file:data/app.db`.
1. Set `DATABASE_URL` (e.g. `libsql://your-db.turso.io`)
2. Set `DATABASE_AUTH_TOKEN` for auth
3. No code changes needed — `@libsql/client` handles both local and remote

### Cloud Upgrade Path
### Real-time Sync

Local SQLite works out of the box. To upgrade to a cloud database:
SSE streams database changes to the UI. When the agent writes to the database via scripts, the UI updates instantly via `useFileWatcher()` which invalidates React Query caches.

1. Set `DATABASE_URL` to a Turso URL (e.g. `libsql://your-db.turso.io`)
2. Set `DATABASE_AUTH_TOKEN` to your Turso auth token
3. No code changes needed — `@libsql/client` handles both local and remote
## Do

- Use Drizzle ORM for structured domain data (forms, bookings, documents)
- Use the `settings` store for app configuration and user preferences
- Use `application-state` for ephemeral UI state that the agent and UI share
- Use `oauth-tokens` for OAuth credentials
- Use core DB scripts (`db-schema`, `db-query`, `db-exec`) for ad-hoc database operations

## Don't

- Don't store structured app data (forms, bookings, records) as individual JSON files when you need querying
- Don't store app state in localStorage, sessionStorage, or cookies
- Don't store structured app data as JSON files
- Don't store app state in localStorage, sessionStorage, or cookies (except for UI-only preferences like sidebar width)
- Don't keep state only in memory (server variables, global stores)
- Don't use Redis or any external state store for app data
- Don't interpolate user input directly into file paths (see Security below)

## Examples by Template

| Template | Files | SQLite |
| ---------- | ------------------------------------------------ | ---------------------------------- |
| **Forms** | `data/settings.json` | forms, responses |
| **Calendar** | `data/settings.json`, `data/availability.json` | bookings |
| **Slides** | `data/decks/*.json` | (not used — decks are JSON files) |
| **Content** | `content/projects/**/*.md`, `*.json` | (not used — content is files) |
| **Videos** | compositions in registry | (not used — state in localStorage) |
- Don't interpolate user input directly into SQL queries — use Drizzle ORM's query builder

## Security

- **Path sanitization** — Always sanitize IDs from request params before constructing file paths. Use `id.replace(/[^a-zA-Z0-9_-]/g, "")` or the core utility `isValidPath()`. Without this, `../../.env` as an ID reads your environment file.
- **Validate before writing** — Check data shape before writing files, especially for user-submitted data. A malformed write can break all subsequent reads.
- **SQL injection** — Use Drizzle ORM's query builder, never raw string interpolation for SQL queries.

## Route Loaders vs API Routes

React Router route `loader` functions can fetch data server-side during SSR. However, the default pattern is **SSR shell + client rendering**: the server renders a loading spinner and the client fetches data from `/api/*` routes via React Query. Only use server `loader` when a page genuinely needs server-rendered content for SEO or og tags (e.g., public booking pages). For all app pages behind auth, stick with the client-side React Query pattern.
- **SQL injection** — Use Drizzle ORM's query builder, never raw string interpolation for SQL queries
- **Validate before writing** — Check data shape before writing, especially for user-submitted data

## Related Skills

- **sse-file-watcher** — Set up real-time sync so the UI updates when data files change
- **scripts** — Create scripts that read/write data files or query the database
- **self-modifying-code** — The agent writes data files as Tier 1 (auto-apply) modifications
- **real-time-sync** — Set up SSE so the UI updates when the database changes
- **scripts** — Create scripts that query the database
- **self-modifying-code** — The agent can also modify the app's source code
38 changes: 38 additions & 0 deletions .agents/skills/ship/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: ship
description: Commit all local changes, run prep, push, check CI, and address PR feedback
user_invocable: true
---

# Ship

Commit all locally changed files, run prep, push to remote, check CI status, and address PR review feedback.

## Steps

1. **Check local changes**: Run `git status` to see all modified/untracked files.

2. **Run prep**: Run `pnpm run prep` to verify build, typecheck, tests, and formatting all pass. If anything fails, fix it before proceeding.

3. **Stage and commit**: Stage all changed files (except `learnings.md` or other gitignored personal files). Write a concise commit message summarizing the changes.

4. **Push**: Push to the current remote branch.

5. **Check CI**: Run `gh pr checks` to see if CI is green. If there are failures, investigate with `gh run view <id> --log-failed`, fix the issues, and push again.

6. **Review PR feedback**: Check for new PR review comments via `gh api repos/{owner}/{repo}/pulls/{number}/comments`. For each comment:
- Be skeptical — not all suggestions are worth implementing
- Fix real bugs regardless of who wrote the code — you own the whole PR
- Reply to comments you disagree with, explaining why
- Only skip code that looks actively mid-work (half-written, clearly incomplete). If it looks done but has a bug, fix it.

7. **Report**: Summarize what was committed, CI status, and any feedback addressed.

## Important

- **Multiple agents run concurrently.** There will often be locally changed files you didn't generate. This is normal. Include everything and move forward. Don't revert other agents' work — but DO fix bugs in it if PR feedback flags real issues. Only leave code alone if it's clearly mid-work (half-written, incomplete). If it looks done but broken, fix it.
- Never commit `learnings.md` or files in `.gitignore`
- If prep fails on code you didn't write, fix it (bad imports, type errors, missing prettier, etc.)
- If PR review comments flag real bugs in other agents' code, fix those too — you own the whole PR
- Run `npx prettier --write` on any files you modify for fixes
- Always run `pnpm run prep` before pushing — it catches what CI will catch
1 change: 0 additions & 1 deletion .claude/scheduled_tasks.lock

This file was deleted.

5 changes: 5 additions & 0 deletions .gemini/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"context": {
"fileName": ["AGENTS.md", "CONTEXT.md", "GEMINI.md"]
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ ralph-loop.local.md

# Wrangler local state
.wrangler
.claude/scheduled_tasks.lock
Loading
Loading