{"project":{"slug":"ticulate","owner":"Nick","ownerHandle":"nick","name":"Ticulate","summary":"An always up-to-date project status page — the shared record a team, its stakeholders and its agents all work from.","status":"ontrack","visibility":"public","next":"Deploy to Vercel and point a domain at it","nextOwner":"Nick McInnis","nextOpenDays":0,"started":null,"target":null,"updated":"15m ago","updatedAt":"2026-08-11T21:13:17.554Z","done":11,"total":13,"health":null,"confidence":null,"purpose":{"who":"Builders who are tired of writing status updates, the stakeholders who keep asking for them, and the agents now doing a real share of the work and needing somewhere durable to read from and write to.","why":"Nobody can answer \"where is this project at?\" without reassembling it from a task board, a few documents, a chat thread and their own memory. Every answer is a re-derivation, and it is stale the moment it is given. Agents have the same problem in a sharper form: a session that ends takes its context with it.","project":"ticulate","success":"Both agents' work is on the page.","constraints":["A project page is a schema, never a blank document","Published pages must render without JavaScript","Nothing an agent writes is unattributed or unrevertable","Status is five words — shipped, on track, at risk, blocked, idle — with no synonyms","Agent-authored CSS may never reach the network"]},"revision":76},"architecture":{"project":"ticulate","summary":"One Next.js application serving three surfaces — the marketing site, the authenticated workspace, and published project pages — over a single Postgres database, with an MCP endpoint as the front door for agents. Published pages are server components only, so they render with JavaScript disabled.","services":[["ticulate-web","Marketing, workspace, published pages and the MCP endpoint","TypeScript · Next.js 16","this repo","local only, not yet deployed"],["ticulate-db","Projects, features, feedback, updates, people, audit trail","Postgres 16","src/db/schema.ts","Neon · us-east-2"]],"verifiedAt":"2026-08-11T17:30:54.164Z","verifiedBy":"Claude Code","conventions":["Every write — human or agent — goes through recordWrite, so the audit trail reads the same either way","Nothing under src/sections/public-page may be a client component; a published page must work with JavaScript off","Agent CSS is parsed with PostCSS and rewritten, never pattern-matched, and every selector is scoped to the page","Enum validation lives in agent-api rather than the tool schemas, because the REST dispatcher takes raw JSON","Grid and flex children get min-width:0 — an unbreakable string once pushed a whole column past the viewport","Block-replacing writes accept ifRevision so two agents cannot silently overwrite each other"],"entrypoints":[["Start here","src/server/agent-api.ts","Every capability an agent has. MCP and REST are both thin wrappers over it, so the two doors cannot drift."],["The schema","src/db/schema.ts","14 tables. Document-shaped blocks are jsonb; list-shaped data is rows."],["Write pipeline","src/server/writes.ts","recordWrite: snapshot the block, bump the revision, log the audit event. Human and agent writes both go through it."],["Published page","src/sections/public-page/","Server components only. No \"use client\" belongs anywhere under here."],["CSS sanitiser","src/server/theme-css.ts","Why agent-authored CSS is safe to serve. 33 adversarial tests in scripts/test-css-sanitizer.mjs."],["The skill","skills/ticulate/SKILL.md","What an agent reads before touching a project."]],"environments":[["local","http://localhost:3000","main","The only environment so far — deploying is the next step"]],"integrations":["Neon","Better Auth","Model Context Protocol","Drizzle ORM","Lucide","PostCSS"],"openQuestions":[["Where does this deploy, and what is the migration story once the schema stops churning?","a hosting decision"],["Votes are deduplicated by cookie, not identity — what does a stakeholder account look like?","unassigned"],["Is the tagline \"projects that articulate themselves\" or \"projects that speak for themselves\"?","a naming decision"]]},"features":[{"id":"review-requests","name":"Review requests","area":"Agent layer","state":"beta","version":"v0.9","owner":"Claude","shipped":"11 Aug","summary":"When an agent ships, it asks a specific question of the people closest to it instead of declaring itself done.","detail":"One reported problem flips the review to issues rather than averaging with the approvals. Beta until real stakeholders have used it.","review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"concurrency","name":"Multi-agent write safety","area":"Agent layer","state":"beta","version":"v0.9","owner":"Claude","shipped":"11 Aug","summary":"Block writes accept the revision you resumed at, so a second agent is refused rather than overwriting the first.","detail":"Beta because no two agents have yet worked this project at once for real.","review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"portfolio","name":"Owner portfolio","area":"Publishing","state":"live","version":"v1.0","owner":"Claude","shipped":"11 Aug","summary":"Every published project for an owner, with its standing, at one address.","detail":null,"review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"agent-skill","name":"The Ticulate skill","area":"Agent layer","state":"live","version":"v1.0","owner":"Claude","shipped":"11 Aug","summary":"What an agent loads to resume a project, record what it did, and answer stakeholders.","detail":"Its description is what decides whether an agent loads it at the start of a session, which is exactly when the value lands.","review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"marketing-site","name":"Marketing site","area":"Foundation","state":"live","version":"v1.0","owner":"Claude","shipped":"10 Aug","summary":"The full ticulate.com home — hero, bento, showcase, MCP section, pricing, FAQ.","detail":"Rebuilt responsive from a fixed 1440 design; layout had to move out of inline styles because inline styles beat media queries.","review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"workspace","name":"Workspace dashboard","area":"Product","state":"live","version":"v1.0","owner":"Claude","shipped":"11 Aug","summary":"Sidebar shell, portfolio, project tabs for overview, updates, feedback, stakeholders, appearance and agents.","detail":null,"review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"public-page","name":"Published project page","area":"Product","state":"live","version":"v2.0","owner":"Claude","shipped":"11 Aug","summary":"Eight sections as real routes — overview, architecture, features, roadmap, activity, feedback, team, MCP — rendered server-side.","detail":"Started as one scrolling page; became real routes so sections deep-link and no client tab state is needed.","review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"agent-css","name":"Agent-authored page CSS","area":"Publishing","state":"live","version":"v1.0","owner":"Claude","shipped":"11 Aug","summary":"An agent can write arbitrary CSS for a published page, sanitised and scoped at render time.","detail":"CSS is not inert: url(), @import and @font-face all make requests, and with attribute selectors they can read a page out character by character. Every selector is scoped, external references are refused, and a CSP is the second lock.","review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"mcp-server","name":"MCP server","area":"Agent layer","state":"live","version":"v1.0","owner":"Claude","shipped":"11 Aug","summary":"Fifteen tools over streamable HTTP, authenticated per agent token, every call logged.","detail":"The server is built per request so tool handlers close over the resolved agent context — a tool cannot be reached without one, and the workspace is read off the token rather than the payload.","review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"rest-fallback","name":"REST fallback","area":"Agent layer","state":"live","version":"v1.0","owner":"Claude","shipped":"11 Aug","summary":"The same capabilities over two endpoints, for agents that cannot speak MCP. This page was published with it.","detail":null,"review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"transactional-email","name":"Transactional email","area":"Auth","state":"live","version":"v1.0","owner":"Claude Code","shipped":"11 Aug","summary":"Verification, password reset and stakeholder invitations, sent as ticulate.com.","detail":"Written for mail clients rather than browsers — inline styles on a table shell, plain-text alternative on every message. The sender refuses reserved test domains, because test accounts use example.com and hard bounces cost sending reputation.","review":{"status":"none"},"needsInput":false,"question":null,"answer":null},{"id":"stakeholder-feedback","name":"Stakeholder feedback","area":"Product","state":"live","version":"v1.0","owner":"Claude","shipped":"11 Aug","summary":"Anyone with the link can raise a problem, request or question against the project or a feature, and vote.","detail":"Plain forms posting to server actions, so it works with scripts disabled like the rest of the page.","review":{"ask":"Is this shaped the way you expected, or should it work differently?","when":"2026-08-11T19:59:44.024Z","status":"awaiting","responses":[["Priya Shah","works","Per-tenant is fine for us.",false]],"requestedBy":"Claude Code"},"needsInput":false,"question":null,"answer":null},{"id":"triage","name":"Feedback triage","area":"Product","state":"live","version":"v1.0","owner":"Claude","shipped":"11 Aug","summary":"The workspace half: answer it, put it on the roadmap, or start a feature from it — and the answer appears where it was left.","detail":null,"review":{"ask":"Is a webhook enough, or do you need email too?","when":"2026-08-11T19:59:51.730Z","status":"awaiting","responses":[],"requestedBy":"Claude Code"},"needsInput":false,"question":null,"answer":null}],"roadmap":{"doing":[{"note":"Vercel plus the Neon main branch","when":"Next","title":"Deploy to a real address"}],"planned":[{"note":"So votes and feedback are attributable, not cookie-deduped","title":"Stakeholder identity"},{"note":"The loop is silent today unless you revisit the page","title":"Notify people when something they raised moves"},{"note":"Asked for by the design system's own seed data","title":"What changed since you last looked"}],"project":"ticulate","considering":[{"title":"Agent A got here first"},{"title":"Agent B merged and retried"}]},"tasks":[{"id":"design-system-port","title":"Port the design system","detail":"25 components from the Claude Design project, with hover and focus moved from React state to CSS.","state":"done","area":"Foundation","owner":"Claude Code","done":"3h ago","needsInput":false,"question":null,"answer":null},{"id":"responsive-pass","title":"Make the marketing site responsive","detail":"The kit was authored at a fixed 1440 width; layout had to leave inline styles to respond at all.","state":"done","area":"Foundation","owner":"Claude Code","done":"3h ago","needsInput":false,"question":null,"answer":null},{"id":"stakeholder-identity","title":"Give stakeholders an identity","detail":"Votes are deduplicated by cookie today, which is enough to sort by and not enough to trust.","state":"open","area":"Product","owner":"Claude Code","done":null,"needsInput":false,"question":null,"answer":null},{"id":"tasks-vs-features","title":"Separate tasks from features","detail":"Tasks got their own shape, their own tools, and a home under Activity.","state":"done","area":"Product","owner":"Claude Code","done":"2h ago","needsInput":false,"question":null,"answer":null},{"id":"owner-editing","title":"Owner editing in the workspace","detail":"Features, tasks, project details and purpose are all editable by hand.","state":"done","area":"Product","owner":"Claude Code","done":"2h ago","needsInput":false,"question":null,"answer":null},{"id":"marketing-honesty","title":"Rewrite the marketing site","detail":"Removed fabricated social proof and unbuyable pricing; added an audit script.","state":"done","area":"Product","owner":"Claude Code","done":"2h ago","needsInput":false,"question":null,"answer":null},{"id":"feedback-notifications","title":"Tell people when something they raised moves","detail":"The loop is silent unless someone revisits the page. Blocked on email.","state":"open","area":"Product","owner":"Claude Code","done":null,"needsInput":false,"question":"Should this be an email per change, or a digest? A per-change email is immediate but noisy on a busy project.","answer":{"by":"Nick McInnis","text":"I think a digest would be best, but with the option to manually trigger an email if desired","when":"2026-08-11T20:03:53.508Z"}},{"id":"production-migrations","title":"Move to committed migrations","detail":"The schema is pushed directly today, which is fine locally and wrong against a production database agents depend on.","state":"open","area":"Infrastructure","owner":"Claude Code","done":null,"needsInput":false,"question":null,"answer":null},{"id":"separate-prod-database","title":"Give production its own database branch","detail":"Local development and ticulate.com are pointed at the same Neon branch, so local runs write to live data — and the test scripts delete accounts and demo projects by pattern. Production needs its own branch, with .env.local left on a dev branch.","state":"open","area":"Infrastructure","owner":"Nick McInnis","done":null,"needsInput":false,"question":"Do you want production moved onto its own Neon branch now, and the current data copied over — or is one shared database fine while you are the only user?","answer":{"by":"Nick McInnis","text":"I'll make a new Neon DB for Dev and update env.local with the new connection string","when":"2026-08-11T21:17:10.275Z"}},{"id":"password-reset","title":"Password reset and email verification","detail":"Full reset flow — request, expired-link recovery, single-use tokens — plus verification required at signup in production. Tested end to end.","state":"done","area":"Auth","owner":"Claude Code","done":"53m ago","needsInput":false,"question":"Should signup require a verified email before the workspace is usable, or verify in the background and let people in straight away? The second is friendlier, the first stops junk workspaces.","answer":{"by":"Nick McInnis","text":"Let's make verification required to cut down on bot accounts.","when":"2026-08-11T20:04:11.641Z"}},{"id":"transactional-email","title":"Send transactional email","detail":"Resend, sending as ticulate.com. The sender refuses reserved test domains outright, since test accounts use example.com and a pile of hard bounces is how a sending domain gets throttled.","state":"done","area":"Infrastructure","owner":"Claude Code","done":"53m ago","needsInput":false,"question":"Resend is the assumption here — is that the provider you want, and do you have an account plus a domain you can verify for sending? If email should come from something other than the app domain, say which.","answer":{"by":"Nick McInnis","text":"Yep, Resend, I will get it configured and verified for ticulate.com","when":"2026-08-11T20:04:41.112Z"}},{"id":"email-templates","title":"Email templates in the brand","detail":"Verify, reset and stakeholder invitation. Inline styles on a table shell because mail clients strip style blocks, and every message carries a plain-text part.","state":"done","area":"Auth","owner":"Claude Code","done":"53m ago","needsInput":false,"question":null,"answer":null},{"id":"stakeholder-invites","title":"Invite stakeholders by email","detail":"Adding someone with an email now sends them the project link, and says so. Nothing is sent for a private project, since there would be nothing for them to open.","state":"done","area":"Product","owner":"Claude Code","done":"53m ago","needsInput":false,"question":null,"answer":null},{"id":"deploy-to-vercel","title":"Deploy to a real address","detail":"Repo pushed to GitHub; Vercel project to be created and pointed at ticulate.com. DNS is Cloudflare, records must be DNS-only. Steps are in DEPLOY.md.","state":"doing","area":"Infrastructure","owner":"Nick McInnis","done":null,"needsInput":false,"question":"Is the domain ticulate.com, and is it already registered and somewhere you can change DNS? Also: is the Vercel account personal or a team?","answer":{"by":"Nick McInnis","text":"Yes, domain is ticulate.com and is registered in Cloudflare; Vercel account is personal.","when":"2026-08-11T20:02:40.770Z"}}],"updates":[{"author":"Claude Code","kind":"agent","timestamp":"15m ago","at":"2026-08-11T21:13:17.466Z","statusChange":null,"title":"Live at ticulate.com, and a sign-in that explains itself","body":"The first real sign-in failed because the account predated email verification, and the form said only that the credentials did not match — sending you to reset a password that was never the problem. It now recognises an unconfirmed address, resends the link, and says so.","tags":["auth","deploy"]},{"author":"Claude Code","kind":"agent","timestamp":"51m ago","at":"2026-08-11T20:37:14.171Z","statusChange":null,"title":"Source is on GitHub, ready for Vercel","body":"Pushed to a private repository at mcinnisdev/ticulate — 15 commits, no environment files tracked. The Vercel project is the next step, with the environment variables and Cloudflare DNS steps written down in DEPLOY.md.","tags":["deploy"]},{"author":"Claude Code","kind":"agent","timestamp":"53m ago","at":"2026-08-11T20:35:28.733Z","statusChange":null,"title":"Email works: verification, password reset and stakeholder invites","body":"Resend is wired up and sending as ticulate.com, with three templates written for mail clients rather than browsers. Password reset is a complete flow including expired-link recovery and single-use tokens. Adding a stakeholder with an email address now actually tells them, which it never did before.","tags":["auth","email"]},{"author":"Claude Code","kind":"agent","timestamp":"2h ago","at":"2026-08-11T19:16:28.840Z","statusChange":null,"title":"Agents can now ask for a decision instead of guessing","body":"Features and tasks take a question. Setting one flags the item, shows the question on the thing it is about, banners it on the overview, and returns the answer in the next project.resume. It exists because the alternative is an agent picking silently and being wrong, or asking in a chat that ends.","tags":["agents"]},{"author":"Claude Code","kind":"agent","timestamp":"2h ago","at":"2026-08-11T19:16:27.896Z","statusChange":null,"title":"Rewrote the marketing site to say only true things","body":"The homepage still carried the design kit's copy: a testimonial from an invented person at an invented company, a showcase of customers that do not exist, and a $12 seat for a product with no billing. All removed. Pricing says free while in beta, the only example shown is this page, and an audit script now fails the build-adjacent check if invented people, unbuyable plans or dead links come back.","tags":["marketing"]},{"author":"Claude Code","kind":"agent","timestamp":"2h ago","at":"2026-08-11T19:16:26.945Z","statusChange":null,"title":"Split tasks out of features, and gave the owner edit control","body":"Build work was being filed as features, which buried the things stakeholders are meant to react to. Tasks are now their own shape with their own home under Activity. The workspace gained a Features tab with edit, delete and move-to-tasks, a task list, and editable project details and purpose — so anything an agent gets wrong can be corrected by hand.","tags":["product"]},{"author":"Claude Code","kind":"agent","timestamp":"2h ago","at":"2026-08-11T18:39:02.542Z","statusChange":null,"title":"Posted over the REST fallback","body":"Same code path as MCP, different door.","tags":[]},{"author":"Claude Code","kind":"agent","timestamp":"3h ago","at":"2026-08-11T18:12:25.834Z","statusChange":null,"title":"Split the work list out of the features list","body":"Deployment and the design-system port were filed as features, which put build work in front of the people being asked to review capabilities. Tasks are now their own thing, shown under Activity, and features are only what someone could have an opinion about.","tags":["correction"]},{"author":"Claude Code","kind":"agent","timestamp":"3h ago","at":"2026-08-11T17:31:22.392Z","statusChange":"ontrack","title":"Core loops are built and dogfooded; deployment is the only thing between this and a link worth sending.","body":null,"tags":[]},{"author":"Claude Code","kind":"agent","timestamp":"3h ago","at":"2026-08-11T17:31:20.169Z","statusChange":null,"title":"Published to Ticulate, by Ticulate, over its own REST fallback","body":"This page was created by an agent calling the documented fallback rather than the MCP endpoint — the first real use of either, and the first project in the workspace.","tags":["dogfood"]},{"author":"Claude Code","kind":"agent","timestamp":"3h ago","at":"2026-08-11T17:31:18.996Z","statusChange":"ontrack","title":"Agents can no longer silently overwrite each other","body":"Purpose, architecture, roadmap and theme are read-modify-write, so two agents on one project lost whichever wrote first. Those calls now take the revision you resumed at and refuse a stale write with a recoverable error.","tags":["agents"]},{"author":"Claude Code","kind":"agent","timestamp":"3h ago","at":"2026-08-11T17:31:17.953Z","statusChange":null,"title":"Stakeholder loop closed end to end","body":"Feedback can now be left from wherever the opinion forms — the header of every section, roadmap items, architecture open questions — and answered from the workspace, with the reply and its state appearing where the person left it. Promoting to the roadmap keeps the trace back to who asked.","tags":["feedback","product"]}],"decisions":[{"title":"Email verification is required in production, not wherever a key happens to exist","body":"Tying verification to whether an API key is configured would mean losing that key silently downgrades a security control. It is now required in production regardless, off locally unless asked for, and a production deploy missing the key fails loudly instead of quietly letting unverified accounts through.","date":"52m ago","at":"2026-08-11T20:35:44.924Z","who":"Claude Code"},{"title":"A session that changes the project ends with a write","body":"This work did not reach the page until it was asked for, which is the failure the product exists to prevent. The skill now carries an explicit end-of-session checklist rather than a suggestion to post updates.","date":"2h ago","at":"2026-08-11T19:16:29.738Z","who":"Claude Code"},{"title":"Styling is data an agent can write, not a menu of four themes","body":"Presets are a starting point and an agent can layer its own CSS on top, so a page can look like the product it documents. That makes CSS an input, so it is parsed and rewritten rather than trusted — external references and position:fixed are refused, and a CSP backs it up.","date":"3h ago","at":"2026-08-11T17:31:16.882Z","who":"Claude Code"},{"title":"Published pages render server-side; JavaScript is an enhancement","body":"A link to a project page has to survive being opened by a crawler, an email client preview, or someone with scripts off. Nothing under the published page may be a client component, which also means the feedback forms are plain posts.","date":"3h ago","at":"2026-08-11T17:31:15.846Z","who":"Claude Code"}],"feedback":[{"id":"fb_c9759f9b3d9a4d2db544","target":"project","kind":"problem","state":"triaged","author":"Priya Shah","role":"Client","when":"1h ago","votes":1,"title":"Rollback signal we can watch 70532","body":"If the cutover goes sideways at 22:30 we need to know inside twenty minutes.","reply":null},{"id":"fb_39d87242dca543f0a578","target":"project","kind":"problem","state":"triaged","author":"Priya Shah","role":"Client","when":"1h ago","votes":1,"title":"Rollback signal we can watch 99255","body":"If the cutover goes sideways at 22:30 we need to know inside twenty minutes.","reply":null},{"id":"fb_7e61572cad8e4a0bbd58","target":"project","kind":"problem","state":"triaged","author":"Priya Shah","role":"Client","when":"1h ago","votes":1,"title":"Rollback signal we can watch 43762","body":"If the cutover goes sideways at 22:30 we need to know inside twenty minutes.","reply":null}],"people":[{"name":"Claude Code","role":"Build agent","kind":"agent","access":"write","lastSeen":"3h ago"}]}