Files
dishant-kumar-thakur 1e78b5e279 feat(phase-10): STRUCT-004 project format detection utility
Task: STRUCT-004
Branch: cline-dev-dishant
Cross-branch notes: none -- no shared dependencies with Richard phase 9/6 work

- ProjectFormatDetector class (injectable filesystem, fully testable)
- detect() async + detectSync() sync variants
- Detects legacy (project.json) vs v2 (nodegx.project.json + _registry.json)
- Returns { format, confidence, indicators } for transparent decision-making
- Scoring system: v2 needs score >= 2 to avoid false positives
- Errors in exists() treated as not-found (graceful degradation)
- V2_INDICATORS / LEGACY_INDICATORS sentinel constants exported
- createNodeDetector() factory for Node.js/test contexts
- Convenience methods: getFormat(), isV2(), isLegacy()
- 30 tests in tests/io/ProjectFormatDetector.test.ts
  - detect(): 12 cases (all combinations, async fs, error handling)
  - detectSync(): 4 cases (incl. throws on async fs)
  - Convenience methods: 7 cases
  - Constants: 3 cases
  - createNodeDetector() integration: 4 cases (real tmp dirs)
- Updated tests/io/index.ts to export detector tests
- Updated PROGRESS-dishant.md: STRUCT-001/002/003/004 all marked complete
2026-02-19 01:19:46 +05:30

9.1 KiB

Phase 10A — AI-Powered Development: Dishant Progress

Developer: Dishant Branch: cline-dev-dishant Last Updated: 2026-02-19


Task Status

Task ID Title Status Notes
STRUCT-001 JSON Schema Definition COMPLETE 8 schemas + validator + tests (33/33 pass)
STRUCT-002 Export Engine Core COMPLETE ProjectExporter + 50 unit tests
STRUCT-003 Import Engine Core COMPLETE ProjectImporter + 55 unit tests incl. round-trip validation
STRUCT-004 Editor Format Detection COMPLETE ProjectFormatDetector + 30 unit tests incl. integration
STRUCT-005 Lazy Loading TODO Unblocked by STRUCT-004
STRUCT-006 Save Logic TODO Unblocked by STRUCT-005
STRUCT-007 Migration Wizard UI TODO Unblocked by STRUCT-006
STRUCT-008 Testing and Validation TODO Unblocked by STRUCT-007
STRUCT-009 Documentation TODO Unblocked by STRUCT-008

STRUCT-001 — JSON Schema Definition COMPLETE

Completed: 2026-02-18

What was built

All files under packages/noodl-editor/src/editor/src/schemas/:

File Schema ID Describes
project-v2.schema.json https://opennoodl.dev/schemas/project-v2.json Root project metadata
component.schema.json https://opennoodl.dev/schemas/component-v2.json Component metadata
nodes.schema.json https://opennoodl.dev/schemas/nodes-v2.json Node graph definitions
connections.schema.json https://opennoodl.dev/schemas/connections-v2.json Connection/wire definitions
registry.schema.json https://opennoodl.dev/schemas/registry-v2.json Component index
routes.schema.json https://opennoodl.dev/schemas/routes-v2.json URL route definitions
styles.schema.json https://opennoodl.dev/schemas/styles-v2.json Global styles + variants
model.schema.json https://opennoodl.dev/schemas/model-v2.json Backend data model definitions
  • validator.ts — SchemaValidator singleton, per-schema convenience methods, Ajv v8 + ajv-formats
  • index.ts — re-exports all schemas, validator, TypeScript interfaces
  • tests/schemas/schema-validator.test.ts — 33 test cases, all passing

Key decisions

  • additionalProperties: true on nodes/connections — open-ended by design
  • Port type is oneOf [string, object] — Noodl uses both formats
  • strict: false on Ajv — schemas use description in definitions
  • require() for ajv-formats — avoids TS type conflict

STRUCT-002 — Export Engine Core COMPLETE

Completed: 2026-02-19

What was built

File Purpose
packages/noodl-editor/src/editor/src/io/ProjectExporter.ts Core exporter class + legacy types + helpers
packages/noodl-editor/tests/io/ProjectExporter.test.ts 50 unit tests
packages/noodl-editor/tests/io/index.ts Test barrel export

Architecture

ProjectExporter is pure — no filesystem access. Takes LegacyProject, returns ExportResult { files[], stats }.

Output layout:

nodegx.project.json
nodegx.routes.json        (conditional)
nodegx.styles.json        (conditional)
components/
  _registry.json
  Header/
    component.json
    nodes.json
    connections.json

Key decisions

  1. Pure/filesystem-agnostic — caller writes files
  2. Node tree flattening — recursive tree to flat array with parent/children IDs
  3. Styles/routes files conditional — only produced when content exists
  4. Legacy stateParamaters typo normalised to stateParameters
  5. Metadata split — styles/routes extracted, rest stays in project file
  6. Original legacy path preserved in component.json for round-trip fidelity
  7. Empty parameters: {} omitted from output

STRUCT-003 — Import Engine Core COMPLETE

Completed: 2026-02-19

What was built

File Purpose
packages/noodl-editor/src/editor/src/io/ProjectImporter.ts Core importer class + helpers
packages/noodl-editor/tests/io/ProjectImporter.test.ts 55 unit tests incl. round-trip

Architecture

ProjectImporter is pure — no filesystem access. Takes ImportInput (all file contents), returns ImportResult { project, warnings }.

ImportInput {
  project: ProjectV2File        (nodegx.project.json)
  registry: RegistryV2File      (components/_registry.json)
  routes?: RoutesV2File         (nodegx.routes.json)
  styles?: StylesV2File         (nodegx.styles.json)
  components: Record<path, { component, nodes, connections }>
}

Key functions exported

Function Purpose
unflattenNodes(flat) Reconstructs recursive legacy node tree from flat NodeV2 array
toLegacyName(componentFile, registryPath) Converts v2 path back to legacy name (uses preserved path field)

Key decisions

  1. Pure/filesystem-agnostic — symmetric with ProjectExporter
  2. unflattenNodes — two-pass: build map, then wire children in order
  3. toLegacyName — prefers component.path (preserved original) for perfect round-trip; falls back to reconstruction
  4. stateParametersstateParamaters reversal — restores legacy typo
  5. Metadata merge — routes/styles merged back into project.metadata
  6. Non-fatal warnings — component failures collected, not thrown
  7. Round-trip validated — 20 round-trip tests covering all data types

Test coverage (55 tests)

  • unflattenNodes — 11 cases (empty, single, parent-child, deep nesting, order, field restoration)
  • toLegacyName — 4 cases (path field, fallback, root, cloud)
  • ProjectImporter.import() — 20 cases (basic, metadata, variants, components, nodes)
  • Round-trip (export → import) — 20 cases (all data types, edge cases)

STRUCT-004 — Editor Format Detection COMPLETE

Completed: 2026-02-19

What was built

File Purpose
packages/noodl-editor/src/editor/src/io/ProjectFormatDetector.ts Format detection utility
packages/noodl-editor/tests/io/ProjectFormatDetector.test.ts 30 unit + integration tests

Architecture

ProjectFormatDetector accepts a DetectorFilesystem interface (injectable, testable). Supports both async detect() and sync detectSync().

Detection logic (priority order):

  1. nodegx.project.json + components/_registry.jsonv2 / high confidence
  2. Either v2 indicator alone → v2 / medium confidence
  3. project.json only → legacy / high confidence
  4. Nothing found → unknown / low confidence
  5. Both legacy + v2 → v2 wins (migration in progress)

Key exports

Export Purpose
ProjectFormatDetector Main class
DetectorFilesystem Interface for filesystem injection
V2_INDICATORS Sentinel filenames for v2 format
LEGACY_INDICATORS Sentinel filenames for legacy format
createNodeDetector() Factory using Node.js fs.existsSync
ProjectFormat `'legacy'
FormatDetectionResult { format, confidence, indicators }

Key decisions

  1. Injectable filesystem — no platform singleton dependency, fully testable
  2. Supports both sync and async exists()detectSync() throws if async fs injected
  3. createNodeDetector() factory for Node.js/test contexts
  4. Scoring system — v2 needs score ≥ 2 (nodegx.project.json=2, registry=2, components dir=1)
  5. Errors in exists() treated as "not found" — graceful degradation

Test coverage (30 tests)

  • detect() — 12 cases (all format combinations, async fs, error handling)
  • detectSync() — 4 cases (v2, legacy, unknown, throws on async fs)
  • Convenience methods — 7 cases (getFormat, isV2, isLegacy)
  • Constants — 3 cases
  • createNodeDetector() integration — 4 cases (real filesystem with tmp dirs)

Decisions and Learnings

  • [2026-02-18] Ajv v8 + ajv-formats: use require() for ajv-formats to avoid TS type conflict with root-level ajv-formats package
  • [2026-02-19] Legacy stateParamaters typo (missing 'e') is real — must be preserved in round-trip. Exporter normalises to stateParameters, importer reverses it.
  • [2026-02-19] component.path field is the key to perfect round-trip fidelity — always preserve the original legacy name in the v2 component file
  • [2026-02-19] unflattenNodes needs two passes — first build the node map, then wire children in order using the children ID array
  • [2026-02-19] Format detection: inject filesystem interface rather than using platform singleton — makes the utility testable without Electron context and usable in Node.js scripts/tests