The Pip System
Pips are the universal feedback surface in Writan. All feedback — Tutor questions, craft rule violations, AI generation notes — surfaces through pips. There is no separate feedback UI pattern.
Pip Types
| Colour | Hex | Source | Meaning | Status |
|---|---|---|---|---|
| Orange | #D35400 | Craft engine | Ruleset violation | Implemented |
| Red | #E74C3C | Tutor | Load-bearing dramatic gap | Planned |
| Amber | #E67E22 | Tutor | Enrichment opportunity | Planned |
| Blue | #2980B9 | Tutor | Continuity flag | Planned |
| Purple | #8E44AD | AI generation | Generation note | Planned |
| Green | #27AE60 | Resolved | Dismissed / answered | Implemented |
Currently, only craft pips (orange) are actively created by the system via the writing rules engine. Tutor pips exist in the schema and seed data but have no generation logic yet.
How Craft Pips Are Created
The analyzeNode() function in lib/rules/analyzer.ts:
- Extracts plain text from TipTap JSON
- Runs all enabled mechanical detectors (27 rules)
- Groups violations by rule ID (one pip per rule per node)
- Diffs against existing craft pips to determine creates/deletes
- Deletes open pips for rules that no longer fire (self-healing)
Database Schema
Pips are stored in the pips table:
id uuid primary key
node_id uuid references nodes
type text -- 'tutor_red' | 'tutor_amber' | 'tutor_blue' | 'craft' | 'generation'
status text -- 'open' | 'answered' | 'dismissed' | 'snoozed'
question text -- the feedback text (one line)
detail text -- expanded detail shown in popup
dismissal text -- reason: 'answered' | 'deliberate' | 'not_applicable' | 'revisit'
note text -- writer's note when dismissing
source text -- rule ID (e.g. 'ORW-001') for origin trackingPip Lifecycle
- Created — by the craft engine when a rule violation is detected
- Open — appears as a coloured dot on the node in the tree
- Reviewed — writer clicks to see the question/feedback
- Resolved — writer takes action:
- Answered — the concern was addressed
- Dismissed — with a reason (deliberate, not applicable, revisit)
Audit Trail
When a pip is dismissed, the dismissal is recorded in two places:
- The
pipstable — status updated, dismissal reason and note stored - The node’s
generation.decisionsarray — for the node-level audit trail
This dual-write ensures both a global view of all pips and a per-node history of decisions.
Implementation
PipBadgecomponents render as small coloured dots usingcreatePortal(el, document.body)for z-index stackingPipPopupshows question and detail on clickPipReviewSidebarprovides a filterable list of all pips across the project- Pips appear on node rows in the document tree