feat(kc): KindredDocument class — silo/ directory parse, expose, promote, and demote #45

Open
opened 2026-02-18 21:18:57 +00:00 by forbes · 0 comments
Owner

Summary

Implement KindredDocument, a Python wrapper class that manages the silo/ directory contents across the document lifecycle. This is Layer 2 of the KC Specification — it sits between the C++ format registration (Layer 1) and the Silo workbench integration (Layer 3).

Currently, kc_format.py handles ZIP round-trip preservation and manifest auto-creation but does not parse, expose, or manage silo/ content. KindredDocument fills this gap.

Background

kc_format.py today:

  • Caches silo/ ZIP entries before save and reinjects them after save
  • Auto-creates silo/manifest.json on first save
  • Updates modified_at timestamp on each save
  • Does NOT parse silo/ content on open
  • Does NOT expose metadata as properties on the document
  • Does NOT handle promotion (.fcstd.kc) or demotion (.kc.fcstd)

KindredDocument will:

  • Parse all silo/ entries when a .kc is opened
  • Expose metadata as properties accessible by the Silo tree and viewer widgets
  • Coordinate the full open/save/promote/demote lifecycle
  • Serve as the single integration point between kc_format.py (ZIP I/O) and the viewport system (tree objects, viewer widgets)

Scope

New file: src/Mod/Create/kindred_document.py

Behavior by operation

Operation Action
Open .kc Parse all silo/ entries from ZIP. Expose as structured data. Signal tree builder to create Silo objects.
Open .fcstd No-op — no silo/ directory. KindredDocument is inactive.
Save .kc Serialize current metadata state into silo/ entries. Coordinate with kc_format.py via pre_reinject.
Save .fcstd Skip metadata serialization. (Demotion stripping is handled by Layer 1 C++ or post-save hook.)
Promote .fcstd.kc Generate silo/manifest.json with new UUID. Optionally prompt for schema field selection if connected to Silo.

Behavior by silo/ entry

Entry On open On save On promote
manifest.json Read UUID, origin, revision, schema version Update modified_at and revision_hash Generate with new UUID
metadata.json Load schema fields, tags, lifecycle Serialize current state from viewer edits Generate with defaults from selected schema
history.json Load local revision log Append entry if document is dirty Initialize empty
approvals.json Load ECO/approval snapshot Pass-through (server authoritative) Initialize empty
dependencies.json Resolve assembly links by UUID Update from current assembly link objects Generate from assembly link scan
macros/ Register embedded macros Serialize bound macros Empty directory
jobs/ Load job definitions for display Serialize job YAML edits Empty directory
thumbnails/ Load Silo-rendered previews No-op (server generates these) Empty directory
inspection/ Load GD&T / tolerance data Serialize annotations Empty directory (deferred to Tier 5)

Integration with kc_format.py

KindredDocument registers a pre_reinject callback (added by Phase 1, issue #37) that receives the silo/ cache dict before ZIP write. It serializes current state from all Silo tree objects into the cache, ensuring edits made in viewer widgets are persisted.

Integration with silo_document.py

SiloDocumentObserver (Phase 1) handles the signal hooks (slotCreatedDocument, etc.). KindredDocument is instantiated by the observer and attached to each .kc document. The observer delegates lifecycle events to KindredDocument.

Key design decisions

  1. One KindredDocument per open .kc document: Stored as a module-level dict keyed by doc.Name. Cleaned up on slotDeletedDocument.

  2. Lazy parsing: silo/ entries are parsed on first access, not all at once on open. This avoids blocking the UI for large .kc files with many entries.

  3. Schema validation on open: If metadata.json references a schema and Silo is connected, fetch the schema descriptor for field validation. If offline, skip validation and display raw values.

  4. Promotion dialog: When promoting .fcstd.kc, show a dialog that allows the user to:

    • Select a schema (fetched from GET /api/schemas if connected)
    • Enter initial metadata fields
    • Optionally link to an existing Silo item by UUID
    • Or create a new Silo item

Files to modify

File Change
src/Mod/Create/kindred_document.py New — KindredDocument class
src/Mod/Create/silo_document.py Instantiate KindredDocument for .kc documents, delegate lifecycle events
src/Mod/Create/InitGui.py Ensure proper registration ordering
src/Mod/Create/CMakeLists.txt Add kindred_document.py to install list

Acceptance criteria

  • Opening a .kc with populated silo/metadata.json exposes schema fields to the viewer system
  • Opening a .kc with all silo/ entry types correctly parses each one
  • Opening an .fcstd does not create a KindredDocument instance
  • Saving a .kc persists metadata changes from viewer edits across close/reopen cycles
  • Promoting an .fcstd to .kc assigns a UUID and creates silo/manifest.json
  • Promotion dialog allows schema selection when connected to Silo
  • Promotion works offline (creates manifest with minimal defaults)
  • KindredDocument is cleaned up when a document is closed
  • Round-trip: open .kc → edit metadata via viewer → save → reopen → edits persisted

Dependencies

  • Phase 1 foundation (#37) — for SiloDocumentObserver and pre_reinject hook
  • Layer 1 C++ format registration (#44) — for file dialog support (can proceed in parallel; promotion dialog is useful even without formal .kc dialog registration)

References

  • docs/KC_SPECIFICATION.md §6 Layer 2 (Silo Metadata Layer)
  • src/Mod/Create/kc_format.py — existing ZIP round-trip logic
## Summary Implement `KindredDocument`, a Python wrapper class that manages the `silo/` directory contents across the document lifecycle. This is Layer 2 of the KC Specification — it sits between the C++ format registration (Layer 1) and the Silo workbench integration (Layer 3). Currently, `kc_format.py` handles ZIP round-trip preservation and manifest auto-creation but does **not** parse, expose, or manage `silo/` content. `KindredDocument` fills this gap. ## Background `kc_format.py` today: - Caches `silo/` ZIP entries before save and reinjects them after save - Auto-creates `silo/manifest.json` on first save - Updates `modified_at` timestamp on each save - Does NOT parse `silo/` content on open - Does NOT expose metadata as properties on the document - Does NOT handle promotion (`.fcstd` → `.kc`) or demotion (`.kc` → `.fcstd`) `KindredDocument` will: - Parse all `silo/` entries when a `.kc` is opened - Expose metadata as properties accessible by the Silo tree and viewer widgets - Coordinate the full open/save/promote/demote lifecycle - Serve as the single integration point between `kc_format.py` (ZIP I/O) and the viewport system (tree objects, viewer widgets) ## Scope ### New file: `src/Mod/Create/kindred_document.py` ### Behavior by operation | Operation | Action | |-----------|--------| | Open `.kc` | Parse all `silo/` entries from ZIP. Expose as structured data. Signal tree builder to create Silo objects. | | Open `.fcstd` | No-op — no `silo/` directory. `KindredDocument` is inactive. | | Save `.kc` | Serialize current metadata state into `silo/` entries. Coordinate with `kc_format.py` via `pre_reinject`. | | Save `.fcstd` | Skip metadata serialization. (Demotion stripping is handled by Layer 1 C++ or post-save hook.) | | Promote `.fcstd` → `.kc` | Generate `silo/manifest.json` with new UUID. Optionally prompt for schema field selection if connected to Silo. | ### Behavior by `silo/` entry | Entry | On open | On save | On promote | |-------|---------|---------|------------| | `manifest.json` | Read UUID, origin, revision, schema version | Update `modified_at` and `revision_hash` | Generate with new UUID | | `metadata.json` | Load schema fields, tags, lifecycle | Serialize current state from viewer edits | Generate with defaults from selected schema | | `history.json` | Load local revision log | Append entry if document is dirty | Initialize empty | | `approvals.json` | Load ECO/approval snapshot | Pass-through (server authoritative) | Initialize empty | | `dependencies.json` | Resolve assembly links by UUID | Update from current assembly link objects | Generate from assembly link scan | | `macros/` | Register embedded macros | Serialize bound macros | Empty directory | | `jobs/` | Load job definitions for display | Serialize job YAML edits | Empty directory | | `thumbnails/` | Load Silo-rendered previews | No-op (server generates these) | Empty directory | | `inspection/` | Load GD&T / tolerance data | Serialize annotations | Empty directory (deferred to Tier 5) | ### Integration with `kc_format.py` `KindredDocument` registers a `pre_reinject` callback (added by Phase 1, issue #37) that receives the `silo/` cache dict before ZIP write. It serializes current state from all Silo tree objects into the cache, ensuring edits made in viewer widgets are persisted. ### Integration with `silo_document.py` `SiloDocumentObserver` (Phase 1) handles the signal hooks (`slotCreatedDocument`, etc.). `KindredDocument` is instantiated by the observer and attached to each `.kc` document. The observer delegates lifecycle events to `KindredDocument`. ## Key design decisions 1. **One `KindredDocument` per open `.kc` document**: Stored as a module-level dict keyed by `doc.Name`. Cleaned up on `slotDeletedDocument`. 2. **Lazy parsing**: `silo/` entries are parsed on first access, not all at once on open. This avoids blocking the UI for large `.kc` files with many entries. 3. **Schema validation on open**: If `metadata.json` references a schema and Silo is connected, fetch the schema descriptor for field validation. If offline, skip validation and display raw values. 4. **Promotion dialog**: When promoting `.fcstd` → `.kc`, show a dialog that allows the user to: - Select a schema (fetched from `GET /api/schemas` if connected) - Enter initial metadata fields - Optionally link to an existing Silo item by UUID - Or create a new Silo item ## Files to modify | File | Change | |------|--------| | `src/Mod/Create/kindred_document.py` | New — `KindredDocument` class | | `src/Mod/Create/silo_document.py` | Instantiate `KindredDocument` for `.kc` documents, delegate lifecycle events | | `src/Mod/Create/InitGui.py` | Ensure proper registration ordering | | `src/Mod/Create/CMakeLists.txt` | Add `kindred_document.py` to install list | ## Acceptance criteria - [ ] Opening a `.kc` with populated `silo/metadata.json` exposes schema fields to the viewer system - [ ] Opening a `.kc` with all `silo/` entry types correctly parses each one - [ ] Opening an `.fcstd` does not create a `KindredDocument` instance - [ ] Saving a `.kc` persists metadata changes from viewer edits across close/reopen cycles - [ ] Promoting an `.fcstd` to `.kc` assigns a UUID and creates `silo/manifest.json` - [ ] Promotion dialog allows schema selection when connected to Silo - [ ] Promotion works offline (creates manifest with minimal defaults) - [ ] `KindredDocument` is cleaned up when a document is closed - [ ] Round-trip: open `.kc` → edit metadata via viewer → save → reopen → edits persisted ## Dependencies - Phase 1 foundation (#37) — for `SiloDocumentObserver` and `pre_reinject` hook - Layer 1 C++ format registration (#44) — for file dialog support (can proceed in parallel; promotion dialog is useful even without formal `.kc` dialog registration) ## References - `docs/KC_SPECIFICATION.md` §6 Layer 2 (Silo Metadata Layer) - `src/Mod/Create/kc_format.py` — existing ZIP round-trip logic
forbes added the enhancement label 2026-02-18 21:18:57 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kindred/silo-mod#45