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

ColourHexSourceMeaningStatus
Orange#D35400Craft engineRuleset violationImplemented
Red#E74C3CTutorLoad-bearing dramatic gapPlanned
Amber#E67E22TutorEnrichment opportunityPlanned
Blue#2980B9TutorContinuity flagPlanned
Purple#8E44ADAI generationGeneration notePlanned
Green#27AE60ResolvedDismissed / answeredImplemented

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:

  1. Extracts plain text from TipTap JSON
  2. Runs all enabled mechanical detectors (27 rules)
  3. Groups violations by rule ID (one pip per rule per node)
  4. Diffs against existing craft pips to determine creates/deletes
  5. 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 tracking

Pip Lifecycle

  1. Created — by the craft engine when a rule violation is detected
  2. Open — appears as a coloured dot on the node in the tree
  3. Reviewed — writer clicks to see the question/feedback
  4. 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:

  1. The pips table — status updated, dismissal reason and note stored
  2. The node’s generation.decisions array — 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

  • PipBadge components render as small coloured dots using createPortal(el, document.body) for z-index stacking
  • PipPopup shows question and detail on click
  • PipReviewSidebar provides a filterable list of all pips across the project
  • Pips appear on node rows in the document tree