Browse by Topic
Phase 1: Concept & Planning
Started in a hand-written idea-notebook on my desk. The premise: combine AI-generated prompts with journaling and visual inspiration – not to automate creativity, but to make space for the slow shaping of ideas.
- Concept: AI prompts + journaling + visual inspiration as a sketchbook, not a production tool
- Philosophy: AI as companion, not replacement
- Tech stack undecided (React? HTMX? Firestore? SQLite?)
- MVP scope: prompt generator, inspiration board, journal system, mock mode
- Pain points: blank-page syndrome, fragmented capture, useful musings buried in chat threads
- Learning thesis: use AI lightly on familiar Python backend, more heavily as a co-programmer on first-time React – could AI accelerate learning a new tool?
Phase 2: Architecture Design
A graph was the obvious shape – the real work was deciding what kind. Five typed nodes, five semantic edge verbs, and hierarchy treated as a view over the graph rather than a separate structure. The backend, frontend, and database stack crystallised here too.
- Tech Stack: FastAPI + React + Vite + Firestore
- Graph Model: five typed nodes (Thought, Prompt, Insight, Result, Journal)
- Edge Types: five semantic verbs (includes, inspires, clarifies, expands, evolves) plus a hierarchical flag
- Design Call: hierarchy treated as a projection over the semantic graph, not a separate edge type
- Two-repo setup:
inpromptout-api+inpromptout-web
Phase 3: Early Implementation
Building the foundation. FastAPI backend with Firestore, basic React frontend, graph traversal logic. First commits, first features, first learning curve.
- FastAPI project structure with repository pattern
- Firestore collections for nodes and edges
- Basic graph traversal implementation (10–13 August)
- OpenAI service scaffolding with mock mode
- React frontend initialization with Vite
Phase 4: Tree Component Birth
The core UI: a drag-and-drop hierarchical tree built with @dnd-kit.
Optimistic updates, a debounced mutation queue, and tombstone-based soft deletes
made it feel instant. The technical debt was accepted deliberately – refactoring
would come in Phase 6.
- @dnd-kit: Insertion line indicators, depth-aware drag-and-drop
- NodeCapabilities system: Frontend-only parent-child validation rules
- NodeWorkflowManager: Type-safe tree operations including circular reference detection
- Optimistic updates: TempId → RealId mapping, debounced mutation queue
- Tombstone pattern: Soft deletes with server reconciliation, no UI flicker
Phase 5: Performance Crisis
A ~1 second drag activation delay made the tree feel broken. Five optimisation attempts – memoisation, prop stabilisation, collision detection changes – improved render times dramatically but didn't touch the root cause. Chrome DevTools traced it to @dnd-kit's sensor initialisation sweeping bounding rects across every droppable element on pointer down. The fix required scoping trees to individual thought subgraphs rather than full journals – a direction that landed in Phase 7.
- Root cause: @dnd-kit sensor initialisation cost scales with droppable element count
- Render win: React.memo + prop stabilisation reduced node render times from 2–10ms to <0.1ms
- Drag lag: ~1 second, persisted – switching libraries would have meant rewriting the tree
- Resolution: Per-thought subgraph scoping via
filterSubgraph(), implemented Phase 7 - Honest note: Some of the memoisation work contained a code smell –
useMemoused whereuseCallbackbelongs
Phase 6: The Great Refactor
A deliberate pause to pay down structural debt. Dead code deleted, a unified mutations facade introduced, and a camelCase crisis untangled – the result of mixing Python and TypeScript conventions across a dual-stack project for the first time. Hook extraction was planned but deferred; the component waited.
- Cleanup: Deleted EdgeManager, DragPerformanceTest, backup files – boundaries that hadn't emerged naturally from the code
- Mutations facade: Single typed interface (
MutationsContract) replacing scattered queue imports across the UI - camelCase fix: Standardised TypeScript to camelCase throughout;
transformOperationToSnakeCase()added at the API boundary - Hierarchical edges:
is_hierarchicalflag formalised, single-parent enforcement added at API level - Hook extraction: Planned (
useTreeSync,useTreeOperations,useDragDropHandlers) – deferred until Phase 12
Phase 7: AI Integration & Independence
October was a feature sprint, but the build raised design questions alongside each implementation: how to scope AI context without diluting it, how to wrap a synchronous SDK in an async service, how to keep suggestions grounded in confirmed state. The OpenAI service shipped at the API layer (Sep 30), followed by dual-mode node creation, the journal workspace, Thoughts-as-Pages UI, and optimistic creation. The Phase 5 drag-lag resolved as a side effect of the Thoughts-as-Pages scoping change.
- OpenAI service:
generate_suggestions()withasyncio.to_thread()wrapping and mock/live toggle - Context building: Parent + siblings + grandparent neighbourhood – foundation for Phase 9 privacy controls
- Dual-mode creation: Left button = manual editor; right button = AI suggestion pre-filled
- Thoughts-as-Pages: Sidebar thought navigation with
filterSubgraph()– resolves Phase 5 drag lag - Optimistic creation:
tempIdassigned client-side; mutation queue replaces on server response - AI loading states: Immediate editor with animated messages while suggestions fetch
Phase 8: Security & Authentication
A systematic retrofit: Firebase authentication added across 39 files in a single planned pass, then a security sweep that caught a cross-tenant cache collision risk, an unvalidated AI context endpoint, and update paths that accepted privileged fields. The Radiant design system landed at the end of the month – a deliberate motivation reset after weeks of backend hardening.
- Firebase Auth (API):
get_current_userFastAPI dependency with Firebase token verification and auto-provisioning - Firebase Auth (Web): Modular
src/features/auth/feature directory with provider/context/hook separation - Anti-enumeration pattern:
ensure_owner()returns 404 for both "not found" and "wrong user" - AI context ownership: Chunked Firestore batch queries validate all suggestion context nodes before OpenAI call
- Field protection: Typed update schemas with
extra = "forbid"; PATCH endpoints removed entirely - Cross-tenant cache fix: Idempotency cache keys changed from
op_id→{user_id}:{op_id} - Edge ownership: Both source and target nodes validated on edge creation
Phase 9: Privacy, Polish & Final Push
Authentication was live and the feature set felt complete – but two things stood between the current state and a launch: explicit AI context controls, and the N+1 query problem flagged since Phase 3. Both landed here, alongside schema hardening across every update endpoint.
- AI Context Model: Three-tier visibility per node –
private(never sent),visible(included on request),pinned(always present) - Filtering at the boundary: AI context enforced at the suggestion endpoint only; users always see their full graph
- Frontend toggle: UI built before backend wiring – the interaction validated the API contract, not the other way round
- Graph traversal: N+1 replaced with BFS batching; 60+ Firestore queries for 20 nodes reduced to 7–9, traversal time below one second
- Schema hardening: Typed update schemas with
extra = "forbid"andexclude_unset=Trueacross all node types - Not quite ready: Security audit and infrastructure metering still outstanding – Phases 10 and 11
Phase 10: Security Audit
A structured security audit uncovered priority findings from P0 to P6. The most critical: client-supplied user IDs in mutation requests. The invite system, admin dashboard, and kill switch followed – shifting the project from "it works" to "it's safe."
- Security Audit: Prioritised findings (P0–P6) with test coverage for each
- Critical Fix: Removed client-supplied userId from mutations and edges
- Invite System: Invite-only registration with email validation and PII-safe logging
- Admin Dashboard: Invite management with client-side sort and list limits
- Kill Switch: Firestore-backed emergency stop with 30s cached TTL
- Emulator-backed integration testing, priority-tiered test suite (P0–P6)
Phase 11: Metering & Cost Safety
Firestore charges per read and per write. Normal usage has a predictable cost – but error cost is different. A single graph traversal bug could loop reads indefinitely. This phase built a layered protection stack: kill switch, rate limiting, and per-request read and write meters with circuit-breaker semantics.
- Read Meter: Per-request Firestore read budget with ContextVar-backed counting
- Write Meter: Reserve-before-write semantics with refund-on-failure
- Rate Limiter: Per-user hourly budgets (separate read and write buckets)
- Egress Audit: DeleteNodeOp infinite loop, unbounded queries, BFS depth caps
- Test Guards: Monkey-patched Firestore SDK to catch unmetered code paths
- DRY Refactor: Shared
Meterbase class, mutations God function extracted
Phase 12: UI V2 & Launch Prep
The backend was hardened, metered, and tested. Time to make it look intentional. A complete frontend redesign, dark mode, a landing page with interactive demos, and the final deployment push toward real users.
- V2 Workspace: Inline editing, vertical rail with AI weight circles
- Dark Mode: Semantic colour tokens, theme toggle, full coverage
- Landing Page: Interactive demos, "Behind the Build" section, code-split routes
- Auth Polish: Tabbed login, forgot password, Google sign-in
- Bot Protection: Cloudflare Turnstile integration
- Deployment: Docker hardening, Cloud Run config, cold-start resilience