Multi-Glyph Chain Melding Implementation

Issue: #411 Goal: Support linear chains of 3+ glyphs: [ax|py|prompt]

Current State

Phase 1 Complete: Storage + DAG migration (PRs #436, #443)

Phase 2 Complete: Port-aware meldability + multi-directional melding

Phase 3 Complete: Composition extension, consolidation, CSS Grid layout

Architecture Decision

Edge-based DAG structure (DAG-native, no derived fields)

Initial Phase 1 implemented glyphIds: string[] for linear chains. However, QNTX requires multi-directional melding:

Flat arrays cannot represent DAG structures. Phase 1b migrates to edge-based composition.

Final structure (DAG-native):

interface CompositionEdge {
  from: string;           // source glyph ID
  to: string;             // target glyph ID
  direction: 'right' | 'top' | 'bottom';
  position: number;       // ordering for multiple edges same direction
}

interface CompositionState {
  id: string;
  edges: CompositionEdge[];
  x: number;
  y: number;
}

Key principle: No glyph_ids field. Traverse edges to find glyphs (DAG-native thinking).

Rationale:

Implementation Checklist

Phase 1: State & Storage ✅ COMPLETE

Decision: Clean breaking change (no backward compatibility). No melded compositions exist in production yet.

Frontend State ✅

Backend Storage ✅

Storage Tests ✅

Frontend Tests ✅

Phase 1b Prerequisites: Proto Definitions ✅ COMPLETE

Goal: Define composition DAG structure in protobuf as single source of truth (ADR-006).

Approach: Follow ADR-007 pattern (TypeScript interfaces only), ADR-006 pattern (Go manual conversion at boundaries).

Create Proto Definition

Update Proto Generation

Phase 1ba: Backend DAG Migration ✅ COMPLETE

Goal: Migrate database and Go storage layer from composition_glyphs junction table to edge-based DAG structure.

Strategy: Breaking change (drop existing compositions, like Phase 1)

Database Schema

Storage Layer

Backend Tests

Phase 1bb: API & Frontend State Migration ✅ COMPLETE

Goal: Update API handlers and TypeScript state management for edge-based compositions.

Completion: All tests passing (728 total: 352 Go + 376 TypeScript)

Backend API Handlers

Proto Definition (DAG-Native)

Frontend State

Frontend Tests

Phase 1bc: Frontend UI Integration ✅ COMPLETE

Goal: Update meld system and UI to work with edge-based compositions.

Completion: All tests passing (728 total: 352 Go + 376 TypeScript)

Meld System

Integration Testing

Phase 2: Port-Aware Meldability ✅ COMPLETE

Goal: Spatial ports on glyphs defining valid directional connections, multi-directional proximity detection and layout.

Port-aware registry ✅

Multi-directional meld system ✅

Auto-meld result below py ✅

Call site updates ✅

Tests ✅

Manual Testing ✅

Phase 3: Composition Extension ✅ COMPLETE

Goal: Meld a standalone glyph into an existing composition (edges-only: append to leaf / prepend to root). Also fix auto-meld when py is already inside a composition.

Composition ID strategy: Regenerate ID on extend (melded-{from}-{to} with the new edge's endpoints).

Module split ✅

Drag-to-extend ✅

Cross-axis sub-containers ✅

Auto-meld-into-composition ✅

Tests ✅

Manual Testing ✅

Phase 3b: Glyph System Consolidation ✅ COMPLETE

PR: #446

Phase 3c: CSS Grid Layout ✅ COMPLETE

PR: #451 (continues #446)

Goal: Replace flex+sub-container layout with CSS Grid. Three parallel code paths (extendComposition, reconstructMeld, unmeldComposition) that independently managed sub-containers collapse into one shared applyGridLayout() function.

Known issues (filed)

Cleanup

Deferred backend tests:

Manual verification:

Open issues:

Open Questions

  1. Unmeld granularity? Should pulling a middle glyph split the composition in two, or always unmeld everything?
  2. Maximum composition size? No hard limit yet — monitor UX as chains grow.

Design Decisions

py → py chaining:

Edge-based composition IDs:

Cross-axis layout (superseded in Phase 3c):

Meld system module split:

Migration Strategy

Actual approach (Phase 1): Clean breaking change

Since no melded compositions exist in production yet, we opted for a simpler breaking change:

Rationale:

Related Work