From c52c6451697f7f931ca5028b157160d9b608d549 Mon Sep 17 00:00:00 2001 From: Amp Date: Fri, 3 Jul 2026 03:08:24 +0000 Subject: [PATCH 1/3] Clean docs and add Plausible tracking Amp-Thread-ID: https://ampcode.com/threads/T-019f25c9-6992-767a-95b0-ae59792f6e84 Co-authored-by: Adam Krupa --- .env.local.example | 4 ++ AGENTS.md | 28 +++------ NEXT.md | 63 ------------------- README.md | 9 ++- bun.lock | 3 + .../flash-experiments-handoff-2026-06-29.md | 0 .../archive/initial-mvp-prompt.md | 0 .../archive/theo-team-communication-brief.md | 0 DESIGN.md => docs/design.md | 0 docs/next.md | 43 +++++++++++++ PRODUCT.md => docs/product.md | 0 package.json | 1 + src/analytics.ts | 10 +++ src/main.tsx | 1 + src/vite-env.d.ts | 8 +++ 15 files changed, 87 insertions(+), 83 deletions(-) delete mode 100644 NEXT.md rename HANDOFF.md => docs/archive/flash-experiments-handoff-2026-06-29.md (100%) rename MVP_PROMPT.md => docs/archive/initial-mvp-prompt.md (100%) rename Theo's Projects_ Team Communication Rewrite.md => docs/archive/theo-team-communication-brief.md (100%) rename DESIGN.md => docs/design.md (100%) create mode 100644 docs/next.md rename PRODUCT.md => docs/product.md (100%) create mode 100644 src/analytics.ts diff --git a/.env.local.example b/.env.local.example index d98a1f8..c86363a 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,2 +1,6 @@ # Created automatically by `bunx convex dev`: # VITE_CONVEX_URL=https://.convex.cloud + +# Optional: enable Plausible Analytics in browser builds. +# Use the exact site domain configured in Plausible. +# VITE_PLAUSIBLE_DOMAIN=postwork.pcstyle.dev diff --git a/AGENTS.md b/AGENTS.md index c4d543c..059b546 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,30 +76,19 @@ friendly "configure a provider" message instead of crashing. ## Design Context -Strategic design context lives in `PRODUCT.md` (register, users, brand +Strategic design context lives in `docs/product.md` (register, users, brand personality, anti-references, design principles). Visual system is documented in -`DESIGN.md`. These are maintained by the `impeccable` design skill +`docs/design.md`. These are maintained by the `impeccable` design skill (`.agents/skills/impeccable/`); run `$impeccable` for the command menu, or e.g. `$impeccable critique ` / `$impeccable polish `. The concrete visual rules below remain the quick reference. ## Design conventions (style is derived from pcstyle.dev) -Dark terminal/developer aesthetic. Tokens live in `src/index.css` `@theme`: - -- Warm near-black: `--color-bg #0a0a0b`, `--color-surface #121014`, - hairline `--color-border #252327`, text `--color-fg #e8e6e3`, - muted `--color-muted #8a8782`. -- Single accent: deep wine `--color-accent #8c1862` (soft `#b53a82`). One accent - per page — use `accent`, never reintroduce indigo/blue. -- **Monospace everywhere** (`--font-mono`). This is the defining trait. -- Small radii (cards `rounded-lg` 8px, controls `rounded-md` 6px). -- **Lowercase chrome** (nav, buttons, filters, labels). User content keeps its - own casing. -- **No emoji / pictographs** in UI or code. Use mono text affordances - (`/` search prompt, `ai` tag, `active 5d ago`). Typographic arrows (`←` `→`) ok. -- Priority colors are warm/muted state colors in `src/lib/format.ts` - (urgent coral, high gold, normal grey), not the page accent. +Use `docs/design.md` as the canonical visual system. Quick reference: warm +near-black surfaces, deep wine accent, Inter for product UI, mono for code/data +affordances, small radii, lowercase chrome, no emoji/pictographs, and muted +priority state colors from `src/lib/format.ts`. ## Code conventions @@ -114,8 +103,9 @@ Dark terminal/developer aesthetic. Tokens live in `src/index.css` `@theme`: ## Deployment Frontend is a static Vite build (`dist/`) → deploy to Vercel. Convex Cloud hosts -the backend; set the AI env vars on the Convex deployment, and `VITE_CONVEX_URL` -on the frontend host. +the backend; set the AI env vars on the Convex deployment. The committed Vercel +build command injects `VITE_CONVEX_URL`; set `CONVEX_DEPLOY_KEY` and +`VITE_PLAUSIBLE_DOMAIN=postwork.pcstyle.dev` in Vercel. diff --git a/NEXT.md b/NEXT.md deleted file mode 100644 index b43825b..0000000 --- a/NEXT.md +++ /dev/null @@ -1,63 +0,0 @@ -# Next - -Postwork is in a good prototype moment: the strongest shell/composer experiments are being graduated, while the core app already proves posts, nested replies, unread state, search, and agent summaries. The next work should make those flows routable, coherent, and evaluable without re-litigating the in-progress shell changes. - -## 1. make feed triage routable - -**Why it matters:** `HANDOFF.md` explicitly says not to ship priority-first-feed filter links until `FeedPage` accepts URL search params, because `/?priority=urgent` silently no-ops today. The code confirms `src/routes/FeedPage.tsx` keeps `term`, `space`, `priority`, and `onlyUnread` in local `useState`, so filters are not shareable, restorable, or usable by navigation experiments. - -**Done looks like:** the canonical feed reads and writes URL search params for `q`, `space`, `priority`, and `unread`; filter chips update the URL without losing scroll unnecessarily; direct links like `/?priority=urgent&unread=1` produce the expected feed; invalid params are ignored or normalized; search and filter empty states remain clear. - -**Main files / areas:** `src/router.tsx`, `src/routes/FeedPage.tsx`, `src/lib/format.ts`, any experiment or nav entry point that wants to link into filtered feed states. - -**Effort:** M. - -**Risks / dependencies:** coordinate with the concurrent `FeedPage` graduation work before editing. TanStack Router search-param typing should stay simple and strict; avoid making the feed feel like an advanced query UI. - -## 2. consolidate the post composer into one product primitive - -**Why it matters:** the bottom-docked quick-post composer is being graduated, but the app still has multiple creation surfaces with different behavior: `src/components/NewPostDialog.tsx`, `src/components/WallPostDialog.tsx`, the post detail `Composer`, and the space-local composer in `src/routes/SpacePage.tsx`. `src/lib/store.tsx` only creates normal and wall posts in the session overlay, while `src/lib/spaces.tsx` has a separate session-only space post model that cannot open as a durable thread. - -**Done looks like:** one composer vocabulary covers quick posts, normal posts, wall posts, replies, and space posts where appropriate; title/body/priority/space affordances are consistent; created posts navigate to or appear in the relevant durable thread surface; validation and disabled states match across surfaces. - -**Main files / areas:** `src/components/Composer.tsx`, `src/components/NewPostDialog.tsx`, `src/components/WallPostDialog.tsx`, the graduated inline composer component, `src/lib/store.tsx`, `src/lib/spaces.tsx`, `src/routes/SpacePage.tsx`. - -**Effort:** L. - -**Risks / dependencies:** depends on the bottom-docked composer graduation. Keep this as product-flow consolidation, not a backend persistence rewrite, unless the prototype needs persistent demo writes later. - -## 3. make spaces first-class threads, not a parallel mock feed - -**Why it matters:** `convex/schema.ts` already models `spaceId`, `visibility`, orgs, spaces, and memberships, and `convex/spaces.ts` has queries for `feedForSpace`. But the frontend space experience in `src/lib/spaces.tsx` uses baked in-memory orgs/spaces/posts, and `src/routes/SpacePage.tsx` renders space posts as inert `
` cards rather than links into `/posts/$postId`. That splits the product thesis: durable posts in the main feed, mock cards in shared spaces. - -**Done looks like:** shared-space posts use the same post detail, replies, unread, priority, search, and agent-summary mechanics as normal posts; visibility is legible in the UI; org-only posts have a real viewer rule; space feeds can be linked and filtered without duplicating post-card logic. - -**Main files / areas:** `convex/schema.ts`, `convex/posts.ts`, `convex/spaces.ts`, `src/lib/spaces.tsx`, `src/routes/SpacesPage.tsx`, `src/routes/SpacePage.tsx`, `src/components/PostCard.tsx`, `src/routes/PostPage.tsx`. - -**Effort:** L. - -**Risks / dependencies:** the public demo is intentionally no-auth and mostly session-overlay driven, while `convex/spaces.ts` assumes backend data. Decide whether spaces are still a flow-design prototype or should become Convex-backed before implementing deeply. - -## 4. strengthen agent-summary and agent-task catch-up loops - -**Why it matters:** the product promise names the AI agent-summary slot as a load-bearing catch-up tool. `src/components/AgentSummary.tsx` only regenerates a post-level summary on demand, and `src/lib/agentTasks.tsx` keeps investigation tasks in memory and injects the agent result as a local reply. That is fine for a demo, but the flow does not yet make stale summaries, new replies, task results, or open questions feel like one coherent catch-up layer. - -**Done looks like:** users can see when a summary is stale after new activity; summary/regenerate copy explains what context is included; agent-task results are easy to find from the post and agents page; failed/no-key states are calm and consistent; the thread transcript sent to the model includes enough nested-reply context to be useful. - -**Main files / areas:** `src/components/AgentSummary.tsx`, `src/components/AgentTasksPanel.tsx`, `src/components/ReplyTree.tsx`, `src/components/SendAgentButton.tsx`, `src/lib/agentTasks.tsx`, `convex/ai.ts`, `convex/agentTasks.ts`. - -**Effort:** M. - -**Risks / dependencies:** live model calls require env keys and should continue to fail gracefully. Do not overbuild autonomous-agent infrastructure before the catch-up reading flow is validated. - -## 5. run a design/accessibility regression pass on the real surfaces - -**Why it matters:** `PRODUCT.md` says triage-at-a-glance signals and quiet confidence are the core persuasion, and `DESIGN.md` sets concrete rules for contrast, one-accent usage, density, and keyboard access. The source tree has many independently built surfaces (`RootLayout`, feed, post detail, agents, spaces, walls, flash lab), so after the shell/composer graduation there is a high risk of inconsistent chrome, casing, spacing, focus, and mobile behavior. - -**Done looks like:** one pass across feed, post detail, new post, replies, agents, spaces, walls, and flash experiments at desktop and narrow widths; obvious focus states and keyboard paths are verified; lowercase chrome and accent discipline are restored; empty/loading/error states use the same voice; any remaining intentionally rough prototype areas are documented. - -**Main files / areas:** `src/routes/*`, `src/components/*`, `src/flashExperiments/*`, `src/index.css`, `PRODUCT.md`, `DESIGN.md`. - -**Effort:** M. - -**Risks / dependencies:** should run after the concurrent shell/composer/deprecation changes land. Requires a running dev server for browser verification, which the repo convention leaves to the user. diff --git a/README.md b/README.md index 4a9874d..0913dde 100644 --- a/README.md +++ b/README.md @@ -135,13 +135,20 @@ Push the repo to GitHub and import it at vercel.com (or run `bunx vercel`). Set: | --- | --- | | Framework preset | **Vite** | | Install command | `bun install` | -| Build command | `bunx convex deploy --cmd 'bun run build'` | +| Build command | `bunx convex deploy --cmd-url-env-var-name VITE_CONVEX_URL --cmd 'bun run build'` | | Output directory | `dist` | | Environment variable | `CONVEX_DEPLOY_KEY` = the `prod:` key from step 2 | +| Environment variable | `VITE_PLAUSIBLE_DOMAIN` = `postwork.pcstyle.dev` | Deploy. Do **not** set `VITE_CONVEX_URL` yourself — `convex deploy --cmd` sets it during the build to point at your production deployment. +Plausible's NPM verifier expects the Vite build to include the same domain you +registered in Plausible. For this Vercel demo, that domain is +`postwork.pcstyle.dev`. Add that custom domain under Vercel Project → Domains, +redeploy after setting `VITE_PLAUSIBLE_DOMAIN`, then verify the Plausible NPM +installation against `postwork.pcstyle.dev`. + ### 5. Seed the production data (one time) ```bash diff --git a/bun.lock b/bun.lock index 9582c91..8e0cd65 100644 --- a/bun.lock +++ b/bun.lock @@ -9,6 +9,7 @@ "@ai-sdk/openai": "^4.0.0-beta.77", "@ai-sdk/openai-compatible": "beta", "@fontsource-variable/inter": "^5.2.8", + "@plausible-analytics/tracker": "^0.4.5", "@shoojs/react": "^0.2.2", "@tanstack/react-router": "latest", "ai": "beta", @@ -115,6 +116,8 @@ "@oxc-project/types": ["@oxc-project/types@0.137.0", "", {}, "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA=="], + "@plausible-analytics/tracker": ["@plausible-analytics/tracker@0.4.5", "", {}, "sha512-6BfAGejXY+YA3Cw6LYT2Zpn4hTxDtPQAawFsYUsQCOg78wIS5C4deAGXTfJffa5VleMWITv5lpJ/EYuQBl1tPA=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.2", "", { "os": "android", "cpu": "arm64" }, "sha512-2cZ+7xRS+DBcuJBJKnfzsbleumJhBqSlJVpuzHC0nTqfd3QQ7Vx2/x5YR/D7cBamKSeWplwo82Fn9lqYUDEMfA=="], "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RkPMJnygxsgOYdkfqgpwY0/Fzm8d0VQe6HGU2/B00Xa9eqdLbrII+DOKAodbJAn3ZL1AJxGHkZRPYazgGY6Ljw=="], diff --git a/HANDOFF.md b/docs/archive/flash-experiments-handoff-2026-06-29.md similarity index 100% rename from HANDOFF.md rename to docs/archive/flash-experiments-handoff-2026-06-29.md diff --git a/MVP_PROMPT.md b/docs/archive/initial-mvp-prompt.md similarity index 100% rename from MVP_PROMPT.md rename to docs/archive/initial-mvp-prompt.md diff --git a/Theo's Projects_ Team Communication Rewrite.md b/docs/archive/theo-team-communication-brief.md similarity index 100% rename from Theo's Projects_ Team Communication Rewrite.md rename to docs/archive/theo-team-communication-brief.md diff --git a/DESIGN.md b/docs/design.md similarity index 100% rename from DESIGN.md rename to docs/design.md diff --git a/docs/next.md b/docs/next.md new file mode 100644 index 0000000..9a53d3b --- /dev/null +++ b/docs/next.md @@ -0,0 +1,43 @@ +# Next + +Postwork now has the core demo loop in place: a centered reading shell, routable feed filters, quick post creation, post detail with nested replies, spaces, walls, flash experiments, and AI summary/task surfaces. The next work should reduce prototype split-brain and make the deployed demo easier to evaluate. + +## 1. make spaces use real post threads + +**Why it matters:** `convex/schema.ts` and `convex/spaces.ts` already model spaces, memberships, visibility, and space feeds, but the frontend still renders shared-space content from `src/lib/spaces.tsx` as session-only cards in `src/routes/SpacePage.tsx`. Those cards do not open durable `/posts/$postId` threads, so spaces feel like a parallel mock product. + +**Done looks like:** creating or viewing a space post uses the same post detail, replies, unread, priority, search, and agent-summary mechanics as the main feed; visibility is legible; space feeds reuse post-card/thread primitives instead of inert local articles. + +**Main files / areas:** `convex/schema.ts`, `convex/posts.ts`, `convex/spaces.ts`, `src/lib/spaces.tsx`, `src/routes/SpacePage.tsx`, `src/routes/SpacesPage.tsx`, `src/components/PostCard.tsx`, `src/routes/PostPage.tsx`. + +## 2. consolidate composer surfaces + +**Why it matters:** the app has multiple creation paths with slightly different behavior: `QuickPostBar`, `NewPostDialog`, `WallPostDialog`, reply `Composer`, and the space-local composer. That makes the prototype harder to reason about and easier to regress. + +**Done looks like:** one composer vocabulary covers posts, replies, wall notes, and space posts where appropriate; validation, disabled states, priority controls, submit shortcuts, and navigation after creation are consistent. + +**Main files / areas:** `src/components/QuickPostBar.tsx`, `src/components/NewPostDialog.tsx`, `src/components/WallPostDialog.tsx`, `src/components/Composer.tsx`, `src/routes/SpacePage.tsx`, `src/lib/store.tsx`. + +## 3. harden the AI catch-up loop + +**Why it matters:** agent summaries and dispatched agent tasks are the clearest differentiator from chat, but they are still split between post summary regeneration, in-memory task state, and local reply injection. + +**Done looks like:** stale summaries are obvious after new replies; summary copy explains included context; agent-task results are easy to find from the post and `/agents`; failed/no-key states remain calm; transcripts include enough nested reply context to be useful. + +**Main files / areas:** `src/components/AgentSummary.tsx`, `src/components/AgentTasksPanel.tsx`, `src/components/ReplyTree.tsx`, `src/components/SendAgentButton.tsx`, `src/lib/agentTasks.tsx`, `convex/ai.ts`, `convex/agentTasks.ts`. + +## 4. prune or graduate flash experiments + +**Why it matters:** the lab still contains shipped, deprecated, and active ideas. That is useful while designing, but the demo should not make evaluators wonder which shell/composer is canonical. + +**Done looks like:** shipped experiments are clearly historical or removed from the active lab; deprecated experiments either explain their lesson briefly or move to archive; the default app shell remains the canonical experience. + +**Main files / areas:** `src/flashExperiments/*`, `src/routes/FlashExperimentsPage.tsx`, `src/components/AppShell.tsx`, `docs/archive/`. + +## 5. run a deployed-demo polish pass + +**Why it matters:** the product promise depends on immediate readability: quiet density, priority at a glance, and no rough prototype seams. + +**Done looks like:** feed, post detail, new post, replies, agents, spaces, walls, and flash lab are checked at desktop and narrow widths; focus states and keyboard paths work; lowercase chrome and single-accent discipline hold; empty/loading/error states share the same voice; Vercel/Plausible/Convex deploy notes are current. + +**Main files / areas:** `src/routes/*`, `src/components/*`, `src/index.css`, `README.md`, `docs/product.md`, `docs/design.md`. diff --git a/PRODUCT.md b/docs/product.md similarity index 100% rename from PRODUCT.md rename to docs/product.md diff --git a/package.json b/package.json index d45860e..ffe09c6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@ai-sdk/openai": "^4.0.0-beta.77", "@ai-sdk/openai-compatible": "beta", "@fontsource-variable/inter": "^5.2.8", + "@plausible-analytics/tracker": "^0.4.5", "@shoojs/react": "^0.2.2", "@tanstack/react-router": "latest", "ai": "beta", diff --git a/src/analytics.ts b/src/analytics.ts new file mode 100644 index 0000000..6aabd01 --- /dev/null +++ b/src/analytics.ts @@ -0,0 +1,10 @@ +import { init } from "@plausible-analytics/tracker"; + +const plausibleDomain = import.meta.env.VITE_PLAUSIBLE_DOMAIN; + +if (plausibleDomain) { + init({ + domain: plausibleDomain, + outboundLinks: true, + }); +} diff --git a/src/main.tsx b/src/main.tsx index 04ba469..3d0e0b2 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -10,6 +10,7 @@ import { SpacesProvider } from "./lib/spaces"; import { router } from "./router"; import { ExperimentProvider } from "./flashExperiments/active"; import { useAuth } from "./shoo"; +import "./analytics"; import "@fontsource-variable/inter/index.css"; import "./index.css"; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..5d81310 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,9 @@ /// + +interface ImportMetaEnv { + readonly VITE_PLAUSIBLE_DOMAIN?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} From 9a14f8e1c8d53481dabf7057703c160c0ceef922 Mon Sep 17 00:00:00 2001 From: pc-style Date: Fri, 3 Jul 2026 12:09:41 +0200 Subject: [PATCH 2/3] Fix Plausible tracker startup handling --- src/analytics.ts | 21 ++++++++++++++++----- vite.config.ts | 10 ++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/analytics.ts b/src/analytics.ts index 6aabd01..22f9fd0 100644 --- a/src/analytics.ts +++ b/src/analytics.ts @@ -2,9 +2,20 @@ import { init } from "@plausible-analytics/tracker"; const plausibleDomain = import.meta.env.VITE_PLAUSIBLE_DOMAIN; -if (plausibleDomain) { - init({ - domain: plausibleDomain, - outboundLinks: true, - }); +declare global { + interface Window { + __postworkPlausibleInitialized?: boolean; + } +} + +if (plausibleDomain && typeof window !== "undefined" && !window.__postworkPlausibleInitialized) { + try { + init({ + domain: plausibleDomain, + outboundLinks: true, + }); + window.__postworkPlausibleInitialized = true; + } catch { + // Analytics is optional; never block app startup. + } } diff --git a/vite.config.ts b/vite.config.ts index ac55ad2..b47f929 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,18 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; +import { fileURLToPath } from "node:url"; + +const plausibleTrackerPath = fileURLToPath( + new URL("./node_modules/@plausible-analytics/tracker/plausible.js", import.meta.url), +); export default defineConfig({ plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@plausible-analytics/tracker": plausibleTrackerPath, + }, + }, server: { port: 5173 }, }); From d705f4c30d5b9b913f8fe4088811e63dc19e71b2 Mon Sep 17 00:00:00 2001 From: pc-style Date: Fri, 3 Jul 2026 12:16:48 +0200 Subject: [PATCH 3/3] Use Plausible package entrypoint --- vite.config.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index b47f929..ac55ad2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,18 +1,8 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; -import { fileURLToPath } from "node:url"; - -const plausibleTrackerPath = fileURLToPath( - new URL("./node_modules/@plausible-analytics/tracker/plausible.js", import.meta.url), -); export default defineConfig({ plugins: [react(), tailwindcss()], - resolve: { - alias: { - "@plausible-analytics/tracker": plausibleTrackerPath, - }, - }, server: { port: 5173 }, });