Snapshot
InPromptOut is a graph-based creative workspace where AI assists within architecture-enforced, user-controlled boundaries.
- Users Private beta – 30 invited testers
- Built 12 months, May 2025 – Apr 2026
- Stack React 19 + Vite 7 + FastAPI + Firestore
- Codebase ~25k source LOC, 349 commits
- Security Multi-AI audit, P0–P6 triage + metering
- Status Invite-only launch alongside this portfolio
Project Overview
InPromptOut is a graph-based creative workspace where AI supports long-form thinking, reflection, and idea development. I built it because chat-based AI tools felt broken for creative workflows: ideas flattened into disposable conversations, creative work scattered across prompts, and authorship blurred in endless threads.
Convinced there was a better way, I drafted a manifesto and envisioned:
-
a digital tool that feels more like a sketchbook than a system
-
a space where ideas evolve without being overridden
-
user-defined AI visibility enforced at the boundary
-
AI as collaborator, never as source of truth
Several months of evenings and weekends in, those ideas had taken shape in code.
The result is a feature-complete MVP built with React/TypeScript and FastAPI (Python). Creativity is treated as a persistent, human-owned graph: thoughts, prompts, insights, and outcomes live as connected nodes you control.
Highlights30-second skim
- Model A graph, not a chat UI Ideas live as connected nodes you own and control, instead of being flattened into a disposable conversation.
- AI Boundaries Three-tier visibility model Every node is Private, Visible or Pinned – deciding what AI can ever see, enforced at the API.
- Performance Registry-backed BFS batching Cut graph traversal from 60+ queries to 7–9, with worst-case latency dropping 19s → <1s.
- Authorization Ownership at every boundary Checks enforced at every repository boundary, including the AI context service, so nothing crosses between users.
- Frontend Optimistic mutation queue A client-side queue coalesces concurrent edits and recovers cleanly on partial failure.
- Safety Audit + metering layer A multi-model P0–P6 security audit and a metering layer that caught a real infinite-loop bug within days of going in.
The Product Idea
InPromptOut treats creativity as a connected network of thoughts and ideas as opposed to a linear conversation.
Instead of a linear chat log, users work inside a graph:
- Thoughts act as roots
- Prompts explore directions
- Insights capture reflection and synthesis
- Results capture media and artifacts
AI can assist, but only with the context the user explicitly allows. That makes authorship explicit: ideas evolve, but nothing advances without human intent.
AI Excluded
AI Excluded
Writing brief
Keep tone grounded
Reference
Chapter outline
Pinned note
Brand voice
How I Built It
The sections below are organised by engineering decision rather than by phase. The thread running through all of them is the same: the user must remain the author of their own thinking, even with AI in the room.
1. Why I Modelled Creativity as a Graph, Not a Chat
Standard AI chat interfaces are accessible, but I found they got in the way of genuine creative work:
- AI confidence is persuasive. It is easy to lose track of your own thinking when verbose answers arrive wrapped in assured, positive language.
- Important thoughts get buried. A chat thread gives you no clear way to track your own thinking process as you work.
- The noise is tiring. Sifting useful signal out of long AI responses takes real effort.
All three are really the same problem: a chat log is a poor place to keep authorship – real thought gets diluted and flattened into a single thread.
A graph offered a better shape. I had spent years teaching students to use mind maps to structure their thoughts, so the model felt natural: ideas branch, connect, evolve and return to each other. Implemented carefully, that structure could let AI participate without taking over the process.
InPromptOut uses a lean MVP model: five node types – Thought, Prompt, Insight, Result and Journal – connected by typed edges. A Thought carries raw intent and nuance; a Prompt explores a direction; an Insight captures reflection or synthesis; a Result holds the media and artefacts the work produces; a Journal roots a body of work and holds reflection on the creative process itself. The edges matter just as much as the nodes, because the relationships between thoughts are part of the thinking too. In short, complexity lives in the relationships just as much as the entities.
Firestore was used for the MVP rather than a native graph database – a pragmatic call under a tight solo timeline, where a graph DB was one unfamiliar tool too many alongside learning React. It proved a consequential trade-off: Firestore is an awkward fit for graph traversal, and closing that gap led to some of the project’s most interesting engineering – batched traversal, mutation routing and cost-safety infrastructure.
Detailed walkthroughs: Phase 1 – Concept · Phase 2 – Architecture
Takeaway:
InPromptOut’s key atomic unit is the relationship between thoughts. A thought is defined by what it connects to and the context it sits in. A linear chat log cannot hold that. Maintaining authorship under AI assistance depends on a data structure that fits the shape of thinking. User-weighted AI context, a feature beta testers responded to very positively, exists only because the structure could carry it.
2. How I Kept AI Inside User-Controlled Boundaries
While the graph is protecting authorship structurally, the AI context model protects it behaviourally. AI can participate in the workspace only where explicitly allowed to do so.
To do this, each node has an explicit AI visibility state:
- Private – never included in AI context.
- Visible – available as context when relevant.
- Pinned – deliberately kept present as important context.
I considered more technical language like “Excluded”, but “Private” better matched the user’s mental model: ‘this part of my thinking stays mine’.
The boundary spans both layers. In the UI, each node shows its current visibility state. In the API, private nodes are stripped before context is assembled, ownership is validated against the authenticated user and pinned nodes are fetched server-side only – the AI provider never receives anything else.
sequenceDiagram
participant FE as Frontend
participant API as API
participant DB as Firestore
participant AI as AI Provider
FE->>API: POST /ai/context
API->>API: strip private nodes
API->>DB: fetch pinned nodes (owner-scoped)
DB-->>API: pinned node data
API->>API: validate ownership
API->>AI: assembled context only
AI-->>API: response
API-->>FE: response
The weighted context system extends that idea. Instead of asking users to write meta-instructions like “pay attention to this”, the interface lets them shape AI influence directly on the graph. A larger marker means a stronger signal. That keeps the interaction fast and visual, without forcing the user into menus or breaking creative flow.
The important constraint is that AI never owns the next step. It cannot see private nodes, cannot expand its own context, and cannot act independently. It can only suggest material from user-approved context, which the user can accept, edit or ignore.
Detailed walkthroughs: Phase 7 – AI Integration · Phase 9 – Privacy, Polish & Final Push
Takeaway:
The AI boundary is a clear part of the architecture. InPromptOut lets AI assist, but only inside a context boundary the user can see, shape and revoke.
3. Why I Let the Architecture Follow the Operation, Not the Data Model
To the user, dragging a node is one fluid operation. To the data model, it is two (or more) updates: the node and its associated edges.
A few weeks before Phase 6, I had mistakenly let the data model’s view shape the frontend. I extracted EdgeManager out of NodeWorkflowManager, trying to give edge-aware operations their own service-layer home. The code resisted. This separation didn’t match a natural seam and refactoring around it involved ‘fighting’ the code rather than improving it. EdgeManager ended up unused outside of tests.
flowchart TB
subgraph fe[Frontend]
direction TB
UI[UI Components]
TMS[TreeMutationService]
Q[mutationQueue]
UI --> TMS
UI --> Q
TMS --> Q
end
subgraph be[Backend]
direction TB
API[POST /api/v1/mutations]
DB[(Firestore)]
API --> DB
end
Q --> API
classDef entry fill:#2a2e3a,stroke:#88a,color:#eee,stroke-width:1px
classDef scattered fill:#3a2a2a,stroke:#c77,color:#eee,stroke-width:1px
classDef api fill:#2a3340,stroke:#789,color:#eee,stroke-width:1px
classDef store fill:#1f1f1f,stroke:#888,color:#ccc,stroke-width:1px
class UI entry
class TMS,Q scattered
class API api
class DB store
flowchart TB
subgraph fe[Frontend]
direction TB
UI[UI Components]
M[mutations
singleton]
UI --> M
end
subgraph be[Backend]
direction TB
API[POST /api/v1/mutations]
DB[(Firestore)]
API --> DB
end
M --> API
classDef entry fill:#2a2e3a,stroke:#88a,color:#eee,stroke-width:1px
classDef facade fill:#223a2d,stroke:#5a9,color:#eee,stroke-width:1px
classDef api fill:#2a3340,stroke:#789,color:#eee,stroke-width:1px
classDef store fill:#1f1f1f,stroke:#888,color:#ccc,stroke-width:1px
class UI entry
class M facade
class API api
class DB store
Phase 6 was the correction. EdgeManager was deleted with useful logic folded back into the mutation layer. The UI’s scattered persistence pathways (direct call to a raw queue and a TreeMutationService wrapping it in places) collapsed into one typed surface: a singleton mutations facade with everything else abstracted away.
EdgeManager was a boundary I’d drawn myself. The same principle kept turning up elsewhere – this time with the boundary drawn by the platform:
- Registry-backed BFS batching. Opening a thought and its surrounding context is one user operation. Firestore was happy to serve it as 60+ sequential lookups. Batching the traversal into 7–9 grouped reads cut worst-case latency from 19 seconds to under one. The addition of a registry gave the system just enough memory of node types and relationship patterns to behave like a lightweight graph database for the operations that mattered.
- Mutation queue coalescing. The queue is the bridge between the UI’s tree state and the API layer. Every keystroke fires
onChangebecause the UI needs that – the queue’s job is to fold the changes into the operation the user actually meant before any update is pushed to the API. The same goes for bigger operations: a delete obsoletes prior edits on that node, temp IDs from optimistic creates resolve against real ones before flush. Both sides of the bridge treat the user’s intent as the unit of operation.
I didn’t push the principle all the way through. The large tree component still wants deeper hook extraction, but its external boundary now matches the user’s operation, so I deferred the internal work rather than destabilise the product for it. On a solo build under a tight timeline, a clean external boundary with contained internal debt was the better trade.
Detailed walkthroughs: Phase 5 – Performance Crisis · Phase 6 – The Great Refactor · Phase 9 – Privacy, Polish & Final Push
Takeaway:
Boundaries that do not match how a user actually uses the system can introduce a great deal of friction, no matter how good the design intention was. When the architecture started to more closely follow user intent, some of the more challenging problems either disappeared or became visible enough to solve.
Graph traversal dropped from 19 seconds to under one, and the frontend mutation surface became something a future maintainer could more easily reason about.
4. Why Feature-Complete Was Not Launch-Ready
By February, the MVP was feature-complete. The graph model held up, AI suggestions worked well, authentication was in place and the core workspace was usable.
AI-assisted development increased the pace of implementation, especially on the React side. While all code was reviewed manually, the velocity had changed the risk profile. More code was landing quickly, across a stack that I was still getting familiar with (on the frontend). A deliberate verification phase seemed prudent at this stage.
The audit treated the backend as the final authority (where I had most experience). Multiple AI models were used as reviewers against vulnerability checklists. Output from this process was not accepted blindly. All findings were collated, triaged and sorted into a P0–P6 priority list which included authentication, ownership checks, AI context boundaries, schema validation and repository access.
That process fed directly into the cost-safety work. Firestore bills by read and write, and graph traversal can become expensive quickly when something loops or fans out unexpectedly. InPromptOut needed limits that lived below ordinary feature code:
- Ownership checks at repository and service boundaries.
- Schema validation to reject fields the client should never control.
- Read and write meters to cap per-request Firestore usage.
- User-level rate limits to reduce abuse and runaway usage.
- A kill switch to stop the system before external calls are made.
Justification for the read meter came within days. It caught a real DeleteNodeOp infinite loop: a bug that would have kept querying Firestore until the platform (not my application) stopped it. I felt relieved that the safety layer was working but simultaneously concerned that the bug existed at all. That is the point of defensive engineering. It does not make a system magically safe; it makes failure visible earlier and reduces the blast radius when something slips through.
Detailed walkthroughs: Phase 10 – Security Audit · Phase 11 – Metering & Cost Safety · Phase 12 – UI V2 & Launch Prep
Takeaway:
Getting every feature working was only half the job. When AI accelerates the rate at which code lands, some mistakes will slip past manual review. Verification must live within the system itself (and not only on the developer paying attention). Ownership checks, schema validation, metering and kill switches are different shapes of the same principle: make failure visible early, and contain its blast radius.
Every feature shipped after this layer was added inherits the safety net for free – cost limits, rate limits and kill switches are no longer feature-level responsibilities.
Behind this MVP sits a longer internal history of iteration and refactoring, which I document in more detail in separate architectural deep dives.
Results & Current Status
InPromptOut is launching as an invite-only beta alongside this portfolio update – currently rolling out to 30 invited testers. The build is feature-complete, security-audited, and metered; public sign-up follows once beta feedback is incorporated.
Engineering outcomes (May 2025 – Apr 2026):
- 349 commits across two repositories (197 backend, 152 frontend); roughly 9,600 lines of Python and 15,600 lines of TypeScript production code (excluding a vendored UI component library), plus ~14,300 lines of backend tests – a ~1.5:1 test-to-source ratio on the API surface.
- 735 backend tests across 43 files, covering auth, ownership, repository boundaries and AI-context enforcement.
- Graph traversal: 60+ Firestore queries → 7–9 (worst-case latency 19s → <1s) via registry-backed BFS batching in Phase 9.
- Patch-based mutations apply granular edits without rehydrating the graph, keeping the optimistic UI responsive.
- Multi-model security audit with P0–P6 triage and defence-in-depth fixes, captured as a dated process artefact in Phase 10.
- Metering layer with per-request budgets and runaway-loop guards – caught a real, non-trivial infinite-loop bug within days of going in (Phase 11).
- 12 documented architectural phases – every major decision captured as a dated audit or implementation plan in the project history.
InPromptOut became an end-to-end exploration of human-centred AI design. Not only through the interface, but through the data model, authorization layer, context assembly, audit process and launch strategy.
What’s Next
The invite-only beta will shape the next round of work: improving the journaling flow, tightening the visual graph interactions, and watching how real users handle AI visibility controls in practice. A particular focus is the quality of the AI-generated content itself – refining how I structure context and API communication to draw out stronger, more relevant responses. I also plan to continue publishing the architectural history behind the project, especially around AI context boundaries, cost safety, and frontend state management.
Let’s Connect
If you’re interested in human-centred AI, thoughtful system design, or how architecture shapes creativity, I’d genuinely like to hear from you.
Prefer to email me directly? hello@lundie.io
Thanks – your message is on its way. I'll get back to you soon.
Something went wrong sending that. Please try again, or email me directly at hello@lundie.io.
Explore more of my work