feat(silo): sync KindredCategory with Silo parts database #22

Open
opened 2026-02-07 16:26:45 +00:00 by forbes · 0 comments
Owner
title, labels, milestone, depends
title labels milestone depends
feat(silo): sync KindredCategory with Silo parts database
feature
silo
appearance
api
Appearance System
feat(ztools): appearance mode toggle UI and observer

Summary

Persist part categories in Silo so engineering appearance data survives across machines, users, and sessions. Silo becomes the source of truth for KindredCategory metadata.

Scope

Silo Server (Go)

  • Add category column to parts table (enum, nullable, defaults to custom_body)
  • Database migration via goose
  • PATCH /api/parts/{id} accepts category field
  • GET /api/parts/{id} returns category field
  • GET /api/parts list endpoint includes category in response
  • Validate category against allowed enum values server-side

Silo Workbench (Python)

  • On Pull: apply Silo category to local KindredCategory property
  • On Push: sync local KindredCategory to Silo category field
  • Conflict detection: flag on push if local and remote categories diverge since last pull
  • Conflict resolution: Silo is source of truth; user prompted to accept remote or force-push local

BOM Integration

  • BOM export includes category column
  • BOM import can set categories from spreadsheet data

Database Migration

-- goose up
ALTER TABLE parts
ADD COLUMN category VARCHAR(20)
DEFAULT 'custom_body'
CHECK (category IN (
    'custom_body', 'fastener', 'structural', 'electrical',
    'seal_gasket', 'bearing_bushing', 'spring_compliant', 'moving_part'
));

-- goose down
ALTER TABLE parts DROP COLUMN category;

API Changes

Update Category

PATCH /api/parts/{id}
Content-Type: application/json

{
    "category": "fastener"
}

Response: 200 OK with updated part object

Get Part (updated response)

GET /api/parts/{id}

{
    "id": "...",
    "part_number": "KS-00042",
    "name": "M6x20 Socket Head Cap Screw",
    "category": "fastener",
    ...
}

Validation Errors

PATCH /api/parts/{id}
{ "category": "invalid_value" }

422 Unprocessable Entity
{ "error": "invalid category: must be one of [custom_body, fastener, ...]" }

Sync Flow

Push:
  local KindredCategory → read property → POST/PATCH to Silo

Pull:
  GET from Silo → category field → set local KindredCategory property
  → trigger observer → re-color if Engineering mode active

Implementation Notes

  • Category enum values are shared constants between Go server and Python workbench
  • Null category in DB treated as custom_body on read
  • Pull applies categories before triggering appearance observer to avoid double-recolor
  • Bulk push/pull operations batch API calls to minimize round trips

File Changes

Server (mods/silo/server/)

internal/
├── models/part.go          # Add Category field
├── handlers/parts.go       # Update PATCH/GET handlers
└── db/migrations/
    └── XXXXXX_add_part_category.sql

Workbench (mods/silo/workbench/)

silo/
├── sync.py                 # Push/pull category sync logic
└── bom.py                  # Category column in BOM export/import

Acceptance Criteria

  • category column exists in parts table after migration
  • API accepts and returns category on PATCH and GET
  • Invalid category values rejected with 422
  • Pull from Silo sets local KindredCategory and triggers recolor
  • Push to Silo persists local KindredCategory
  • Diverged categories flagged on push with resolution prompt
  • BOM export includes category; BOM import sets categories
  • Existing parts with null category default to custom_body
--- title: "feat(silo): sync KindredCategory with Silo parts database" labels: ["feature", "silo", "appearance", "api"] milestone: "Appearance System" depends: ["feat(ztools): appearance mode toggle UI and observer"] --- ## Summary Persist part categories in Silo so engineering appearance data survives across machines, users, and sessions. Silo becomes the source of truth for `KindredCategory` metadata. ## Scope ### Silo Server (Go) - [ ] Add `category` column to `parts` table (enum, nullable, defaults to `custom_body`) - [ ] Database migration via goose - [ ] `PATCH /api/parts/{id}` accepts `category` field - [ ] `GET /api/parts/{id}` returns `category` field - [ ] `GET /api/parts` list endpoint includes `category` in response - [ ] Validate category against allowed enum values server-side ### Silo Workbench (Python) - [ ] **On Pull:** apply Silo `category` to local `KindredCategory` property - [ ] **On Push:** sync local `KindredCategory` to Silo `category` field - [ ] **Conflict detection:** flag on push if local and remote categories diverge since last pull - [ ] **Conflict resolution:** Silo is source of truth; user prompted to accept remote or force-push local ### BOM Integration - [ ] BOM export includes `category` column - [ ] BOM import can set categories from spreadsheet data ## Database Migration ```sql -- goose up ALTER TABLE parts ADD COLUMN category VARCHAR(20) DEFAULT 'custom_body' CHECK (category IN ( 'custom_body', 'fastener', 'structural', 'electrical', 'seal_gasket', 'bearing_bushing', 'spring_compliant', 'moving_part' )); -- goose down ALTER TABLE parts DROP COLUMN category; ``` ## API Changes ### Update Category ``` PATCH /api/parts/{id} Content-Type: application/json { "category": "fastener" } ``` **Response:** `200 OK` with updated part object ### Get Part (updated response) ``` GET /api/parts/{id} { "id": "...", "part_number": "KS-00042", "name": "M6x20 Socket Head Cap Screw", "category": "fastener", ... } ``` ### Validation Errors ``` PATCH /api/parts/{id} { "category": "invalid_value" } 422 Unprocessable Entity { "error": "invalid category: must be one of [custom_body, fastener, ...]" } ``` ## Sync Flow ``` Push: local KindredCategory → read property → POST/PATCH to Silo Pull: GET from Silo → category field → set local KindredCategory property → trigger observer → re-color if Engineering mode active ``` ## Implementation Notes - Category enum values are shared constants between Go server and Python workbench - Null category in DB treated as `custom_body` on read - Pull applies categories before triggering appearance observer to avoid double-recolor - Bulk push/pull operations batch API calls to minimize round trips ## File Changes ### Server (`mods/silo/server/`) ``` internal/ ├── models/part.go # Add Category field ├── handlers/parts.go # Update PATCH/GET handlers └── db/migrations/ └── XXXXXX_add_part_category.sql ``` ### Workbench (`mods/silo/workbench/`) ``` silo/ ├── sync.py # Push/pull category sync logic └── bom.py # Category column in BOM export/import ``` ## Acceptance Criteria - [ ] `category` column exists in `parts` table after migration - [ ] API accepts and returns category on PATCH and GET - [ ] Invalid category values rejected with 422 - [ ] Pull from Silo sets local `KindredCategory` and triggers recolor - [ ] Push to Silo persists local `KindredCategory` - [ ] Diverged categories flagged on push with resolution prompt - [ ] BOM export includes category; BOM import sets categories - [ ] Existing parts with null category default to `custom_body`
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kindred/create#22