Skip to content

Extract core, separate from UI, refactor data models and pipeline. Almost a full rewrite.#3

Open
ssrihari wants to merge 37 commits intomainfrom
zustand-state-management
Open

Extract core, separate from UI, refactor data models and pipeline. Almost a full rewrite.#3
ssrihari wants to merge 37 commits intomainfrom
zustand-state-management

Conversation

@ssrihari
Copy link
Member

No description provided.

ssrihari and others added 30 commits March 17, 2026 20:09
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n files

Move state management from App.tsx (~3500 lines) into two Zustand stores:
- conversation-store.ts: conversations, selections, workflow actions
- ui-store.ts: dialog state, presets, import state, dimensions

Extract workflow pipeline from App.tsx into domain-organized files:
- parse.ts, count-tokens.ts, segment.ts: pre-AI workflow steps
- component-identification.ts: discover component list per dimension
- component-classification.ts: classify each part into a component
- color.ts: assign colors to components
- summarize.ts, analyze.ts: AI summary and analysis generation
- pipeline.ts: event dispatcher with per-event handlers
- runner.ts, types.ts, dimensions.ts: shared infrastructure

App.tsx shrinks to ~1150 lines of JSX, effects, and handler wiring.
Each workflow domain file is a vertical slice (activity + step runner).
The pipeline dispatcher replaces a 390-line monolithic function with a
20-line switch and 9 self-contained event handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the WorkflowRunner class and Activity<T> type. Replace with:
- Notify: plain callback type (id, update) => void
- startStep/endStep/updateState/markComplete: free functions
- timed(): generic async timing wrapper

Reorganize workflow files by domain instead of abstraction layer:
- parse.ts, count-tokens.ts, segment.ts, color.ts, summarize.ts, analyze.ts
  each contain the activity logic + step runner as a vertical slice
- component-identification.ts: discover component list (calls identifyComponents)
- component-classification.ts: classify parts (calls mapComponentsToIds)
- pipeline.ts: event dispatcher + composite sequences

Each domain file calls the core lib directly — no intermediate Activity<T>
wrapper or class instantiation needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Plan 1 - Componentisation Cleanup:
  Split componentisation.ts into component-types, component-identification,
  component-classification, component-coloring. Rename static-componentisation
  to static-components. Replace getComponentisationConfig with direct getAIConfig.
  Rename prompt key "component-mapping" to "component-classification".

Plan 2 - Simplified Pipeline:
  Replace WorkflowEvent enum (9 values) with PipelineStep enum (6 ordered values).
  Replace 8 handler functions + dispatcher with runPipelineFrom(startStep).
  Summary/analysis become standalone on-demand functions.

Plan 3 - First-Class Dimensions:
  Remove legacy top-level component fields (components, componentMapping,
  componentTimeline, componentColors, targetDimension) from WorkflowState.
  dimensions is now the single source of truth. Add accessor helpers
  (getDimension, getDefaultDimension, getAllComponents).

Plan 4 - First-Class Groups:
  Groups become lightweight metadata (Group type with name + fileIds).
  Stored in separate groups store slice, not as concatenated WorkflowStates.
  Remove isGrouped, sourceConversations, messageSourceMap from WorkflowState.

Plan 5 - Store Decomposition:
  Extract buildBaseContext to workflow/context.ts, file drop parsing to
  lib/file-import.ts, exportPromptsAsPreset to lib/export-builder.ts,
  orchestration functions to workflow/orchestrate.ts. Store shrinks from
  788 to 332 lines.

Additional:
  Rename SourceInfo→OriginInfo, messageSourceMap→messageOriginMap (provenance).
  Rename sourceConversations→memberFiles, sourceWorkflowStates→memberWorkflowStates.
  Define Source discriminated union type (FileSource | GroupSource) in source-types.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allows turning off or adjusting reasoning/thinking for OpenAI reasoning
models (gpt-5 series, o-series) via VITE_AI_THINKING=none|low|medium|high.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store methods no longer accept setSelectedId or selectedId parameters.
They just mutate data and return intent (e.g., groupConversations returns
the group ID). App.tsx handles all navigation reactively:

- useEffect auto-selects when selectedId is null or invalid
- Wrapper functions in App.tsx call setSelectedId after store returns
- No more stale closure captures from passing selectedId into async functions

This fixes the bug where conversations would disappear after processing
(caused by setSelectedId being called too late, after workflows completed,
with a stale selectedId value).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract messageOriginMap into separate groupDisplayData memo (remove as-any cast)
- Fix WorkflowDetailModal opening for group entries in sidebar
- Strengthen color prompt to enforce named colors only (no hex codes)
- Apply-prompts-to-all now also copies component list and colors as presets
- Enable summary/analysis generation for groups (stored on Group object)
- Virtual group state includes aiSummary/analysis from Group

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add VITE_AI_BASE_URL and VITE_AI_API_MODE env vars to allow switching
between OpenAI and any OpenAI-compatible provider. Centralize model
creation in ai-config.ts via createModel() helper, removing duplicate
createOpenAI() calls across all pipeline files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logs now show [identifying-components] and [classifying-components] as
distinct phases instead of a single [finding-components]. Step timings
record each separately. The UI progress indicator still shows one
combined "Find components" step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Small cycle button above waffle chart in Components tab. Setting is
shared across WaffleChart, StackedBarChartView, and ComponentComparisonView
via UI store. Default is 0 (unchanged behavior).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ComparisonLegend is a separate function component that didn't have
access to the percentPrecision prop. Now receives it explicitly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Step runners (identify, classify, color) accept optional dimNames[]
  to process only specific dimensions instead of all
- runPipelineFrom passes dimNames through to step runners
- Extract runDimensionSteps to eliminate duplication across
  processNewFile, resumeFromPause, and runPipelineFrom
- Move customPrompt, customColoringPrompt, customComponents from
  WorkflowState top-level into DimensionData (dimensions are now
  self-contained)
- Rewrite applyPromptsToAll to diff per-dimension prompts and only
  reprocess dimensions that actually changed
- App.tsx passes [dimName] when editing prompts/components/colors
  for a single dimension

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add reprocessTarget() to orchestrate.ts: resolves group-or-file,
  fans out to member files for groups, processes single file otherwise
- Replace all if (selectedGroup) { loop } blocks in App.tsx handlers
  with calls to handleReprocessTarget
- Coloring prompt reprocessing now works for groups (was missing)
- Remove stale fields from Group type: customPrompt,
  customColoringPrompt, customSegmentationPrompt, segmentationThreshold
  (component prompts live on member files' dimensions, not the group)
- Remove unused runPipelineFrom import from App.tsx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Components now read from Zustand stores directly instead of receiving
30+ callback props threaded through App.tsx. This eliminates the
controlled+fallback dual-state pattern and makes each component
self-contained.

New files:
- stores/url-store.ts: URL state as a Zustand store (replaces useUrlState hook)
- hooks/useWorkflowActions.ts: extracted handler functions from App.tsx
- lib/message-filters.ts: shared filter logic (was duplicated 3x)

Key changes:
- ConversationList: 36 props → 0 (reads stores directly)
- ConversationView: removed 24 URL-state props and dual-state pattern
- ComponentComparisonView: removed 14 props and 6 local state shadows
- App.tsx: 1278 → 632 lines (layout shell + effects + dialogs only)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pipeline (pipeline.ts) — just the step chain:
- runDimensionSteps: Segment → Identify → (Classify + Color in parallel)
- processNewFile: Parse → CountTokens → Static → runDimensionSteps
- resumeFromPause: runDimensionSteps from Segment

Orchestration (orchestrate.ts) — who runs what, where results go:
- runPipelineFrom, reprocessWithRunner, reprocessTarget (moved from pipeline)
- runWorkflows batch processing (moved from pipeline)
- generateSummaryOnDemand/generateAnalysisOnDemand/rerunSummary (moved)
- New store-level functions: generateAnalysisForTarget,
  generateSummaryForTarget, rerunSummaryForTarget — handle group-vs-file
  dispatch with group-aware notify routing

UI layer (useWorkflowActions.ts) — thin wrappers only:
- All handlers now call store actions, never pipeline functions directly
- Removed buildBaseContext, Notify, runPipelineFrom imports
- Group fan-out handled by orchestrate, not UI code

Also:
- Rename runner.ts → notify.ts (it's progress/state helpers, not a runner)
- Classify + Color run in parallel after Identify (both only need
  component list, not each other)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WorkflowDetailModal now reads conversation data from the store and
calls action functions directly instead of receiving ~20 callback props.

- Props reduced from 28 to 3 (isOpen, onClose, conversationId)
- Reads conv data, group info, member files from useConversationStore
- Calls openPromptEditor, generateAnalysis, etc. from useWorkflowActions
- ConversationList call site simplified from 28 props to 3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ai-config.ts: LanguageModelV2 → LanguageModel (renamed in ai SDK)
- GroupFileOrderEditor: optional chaining on possibly-undefined array access
- url-fetch.ts: non-null assertion on array element
- workflow/types.ts: widen stepTimings to accept sub-step keys
- claude-transcripts-parser: type assertions for Zod union narrowing
  (ClaudeContent catch-all schema prevents TS discriminated union narrowing)
- codex-transcripts-parser: same Zod union narrowing fix
- export-import tests: remove null analysis values (should be undefined)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- WorkflowDetailModal: derive dimensions from first member file for
  groups (was undefined because groups aren't in conversations array)
- openPromptEditor/openComponentsEditor/openColoringPromptEditor: for
  groups, read current prompt from first member file
- applyPrompt/applyComponents/applyColoringPrompt: for groups, update
  all member files' dimensions in the store (was only updating by
  group ID which matched nothing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The diff was only comparing prompts and customComponents between
source and target dimensions. When both had undefined prompts (default),
nothing would run even if the actual component lists were different.

Now compares the actual components array too, so "apply to all" correctly
reprocesses targets whose components differ from the source.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add "segments-asc" and "segments-desc" sort options that flatten
  messages into individual parts sorted by token count
- Change default coloring prompt to request hex codes with a reference
  palette, instead of restricting to named color strings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sion data

Both runClassifyComponents and runAssignColors run in parallel and were
replacing dims[dimName] with a spread of the original object. Whichever
finished last would wipe the other's work, causing either missing
componentMapping (no waffles) or missing componentColors.

Fix: mutate the existing dimData object in place instead of replacing it.

Also add store/dimensions to __debug window object for easier debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each dimension-level step (Identify, Classify, Color) now checks its
inputs against its existing outputs and skips if they match, rather
than relying on the orchestrator to decide what to run. This removes
the branching logic from applyPromptsToAll (segChanged/identifyDims/
colorDims) — it now copies all state from source and runs the full
pipeline, letting each step skip itself.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The expandedDimension state was keyed by dimension name alone, so
expanding "default" on one conversation expanded it everywhere.
Now keyed by "conversationId:dimName" so each is independent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move selection state and API key state from conversation-store to
ui-store. Extract export actions as standalone functions. Remove 7
orchestration passthrough methods from conversation-store — hooks
now call orchestrate functions directly via an external accessor,
making conversation-store a pure data/CRUD layer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reorganize the codebase into a clear layered directory structure:
- model/ — domain types and schemas (schema, types, dimensions, export-schema, presets)
- operations/ — pure transforms (aggregation, token-counting, static-components, etc.)
- parsers/ — pluggable input format adapters
- stages/ — processing pipeline stages with ai/ infrastructure nested inside
- pipeline/ — orchestration (step sequencing, notify, orchestrate)
- stores/ — state management + actions (moved useWorkflowActions here)
- ui/ — React components, hooks, and UI-specific lib files
- lib/ — generic utilities (id-generator)

Each algorithm file and its workflow wrapper are merged into one file per
stage. The dependency rule is enforced: model → nothing, operations → model,
parsers → model, stages → model + operations, pipeline → stages, stores →
pipeline, ui → stores.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move browser I/O (downloadExport, exportPromptsAsPreset) from
  operations/ to ui/lib/export-download.ts
- Move workflow-logger and stage-logger from stages/ai/ to pipeline/
  (they're cross-cutting infrastructure, not AI-specific)
- Replace ProcessingPhase/ProcessingStep with unified Stage and
  StageGroup types; consistent -ing verb forms throughout
- Rename Workflow* types to Pipeline* (PipelineState, PipelineCallbacks,
  PipelineOptions, etc.) to match directory naming
- Rename DimensionData.components to discoveredComponents; add
  getEffectiveComponents() helper to consolidate override logic
- Add I/O concern note to stages/ai/preset-loader.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Write docs/architecture.md: directory structure, layers, dependency
  rules, stage descriptions, pipeline flow, and key concepts
- Archive 12 completed/historical plan docs to docs/archive/
- Update CAPABILITIES.md file locations to new structure
- Update tech-stack.md: useState → Zustand
- Update system-overview.md and dimension-scoped-pipeline-design.md
  with cross-references to architecture.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssrihari and others added 7 commits March 20, 2026 12:29
Each dimension-level stage (identify, classify, color) now operates on a
single DimensionData instead of internally looping over all dimensions.
The pipeline owns the iteration:

- identifyForDimension(conversation, dimData, config, id)
- classifyForDimension(conversation, dimData, config, id)
- colorForDimension(dimData, config, id, presetColors?)

pipeline/pipeline.ts handles: which dimensions to process, parallelism
(classify + color run in parallel per dimension), error collection, and
timing. Stages are now simpler — pure single-dimension logic with no
orchestration concerns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dimension loop in pipeline/pipeline.ts silently skipped all work
when dims["default"] didn't exist (new files start with empty dimensions).

Added ensureDimension() and createEmptyDimension() to model/dimensions.ts
as the canonical way to initialize dimensions. Replaced 7 inline empty
DimensionData literals across stores/actions, pipeline/orchestrate, and
ui/App with the factory function.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stages now return results instead of mutating ctx/dimData. The pipeline
is defined as a PIPELINE array of StageDescriptors, and a runner handles
sequencing, dimension loops, parallelism, logging, timing, and store
updates. Classify+color parallel execution is race-free by construction
— results are merged after both complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the declarative PIPELINE array + runner with an imperative
runPipeline function that reads top-to-bottom like pseudocode.
Absorb all orchestration (reprocessTarget, applyPromptsToAll, batch
processing, on-demand summary/analysis) into pipeline.ts and delete
orchestrate.ts entirely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each stage checks if its work is already done and skips if so.
Callers signal "re-run this stage" by clearing its outputs
(e.g., dim.discoveredComponents = [] to force re-identify).
Delete PipelineStep enum — no longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete processNewFile and resumeFromPause — they were identical
  try/catch wrappers around runPipeline. Error handling is now
  inside runPipeline itself.
- Remove redundant markComplete calls from reprocessTarget and
  applyPromptsToAll — the pipeline's final updateState already
  pushes the complete state.
- Extract makeGroupAwareCallbacks to deduplicate streaming callback
  setup for summary/analysis.
- Fix duplicate ctx.aiSummary = "" in rerunSummaryForTarget.
- Simplify resumePipelinesWithApiKey to call runPipeline directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant