From ceaa3acffe16cd08ca80b1fc1fe60d83e38b6314 Mon Sep 17 00:00:00 2001 From: forbes Date: Tue, 3 Mar 2026 13:35:35 -0600 Subject: [PATCH] docs: update server docs --- docs/src/architecture/ondsel-solver.md | 132 --- docs/src/architecture/overview.md | 78 -- .../architecture/python-source-of-truth.md | 26 - docs/src/architecture/signal-architecture.md | 274 ------- docs/src/architecture/silo-server.md | 11 - docs/src/quicknav/SPEC.md | 441 ---------- docs/src/silo-server/CONFIGURATION.md | 67 +- docs/src/silo-server/GAP_ANALYSIS.md | 79 +- docs/src/silo-server/INSTALL.md | 3 + docs/src/silo-server/MODULES.md | 56 +- .../src/silo-server/PROJECTS_AND_APPROVALS.md | 248 ++++++ docs/src/silo-server/ROADMAP.md | 34 +- docs/src/silo-server/SOLVER.md | 109 +-- docs/src/silo-server/STATUS.md | 18 +- docs/src/silo-server/WORKERS.md | 4 +- docs/src/silo-server/frontend-spec.md | 757 ------------------ docs/src/silo-server/overview.md | 128 --- 17 files changed, 512 insertions(+), 1953 deletions(-) delete mode 100644 docs/src/architecture/ondsel-solver.md delete mode 100644 docs/src/architecture/overview.md delete mode 100644 docs/src/architecture/python-source-of-truth.md delete mode 100644 docs/src/architecture/signal-architecture.md delete mode 100644 docs/src/architecture/silo-server.md delete mode 100644 docs/src/quicknav/SPEC.md create mode 100644 docs/src/silo-server/PROJECTS_AND_APPROVALS.md delete mode 100644 docs/src/silo-server/frontend-spec.md delete mode 100644 docs/src/silo-server/overview.md diff --git a/docs/src/architecture/ondsel-solver.md b/docs/src/architecture/ondsel-solver.md deleted file mode 100644 index 96e6038c46..0000000000 --- a/docs/src/architecture/ondsel-solver.md +++ /dev/null @@ -1,132 +0,0 @@ -# KCSolve: Pluggable Solver Architecture - -KCSolve is the pluggable assembly constraint solver framework for Kindred Create. It defines an abstract solver interface (`IKCSolver`) and a runtime registry (`SolverRegistry`) that lets the Assembly module work with any conforming solver backend. The default backend wraps OndselSolver via `OndselAdapter`. - -- **Library:** `src/Mod/Assembly/Solver/` (builds `libKCSolve.so`) -- **Python module:** `src/Mod/Assembly/Solver/bindings/` (builds `kcsolve.so`) -- **Tests:** `tests/src/Mod/Assembly/Solver/` (C++), `src/Mod/Assembly/AssemblyTests/TestKCSolvePy.py` (Python) - -## Architecture - -``` -┌──────────────────────────────────────────────────┐ -│ Assembly Module (AssemblyObject.cpp) │ -│ Builds SolveContext from FreeCAD document, │ -│ calls solver via SolverRegistry │ -├──────────────────────────────────────────────────┤ -│ SolverRegistry (singleton) │ -│ register_solver(), get(), available() │ -│ Plugin discovery via scan() / scan_default_paths │ -├──────────────┬───────────────────────────────────┤ -│ OndselAdapter │ Python solvers │ Future plugins │ -│ (C++ built-in)│ (via kcsolve) │ (.so plugins) │ -└──────────────┴───────────────────────────────────┘ -``` - -The Assembly module never references OndselSolver directly. All solver access goes through `SolverRegistry::instance().get()`, which returns a `std::unique_ptr`. - -## IKCSolver interface - -A solver backend implements `IKCSolver` (defined in `IKCSolver.h`). Only three methods are pure virtual; all others have sensible defaults: - -| Method | Required | Purpose | -|--------|----------|---------| -| `name()` | yes | Human-readable solver name | -| `supported_joints()` | yes | List of `BaseJointKind` values the solver handles | -| `solve(ctx)` | yes | Solve for static equilibrium | -| `update(ctx)` | no | Incremental re-solve after parameter changes | -| `pre_drag(ctx, parts)` | no | Begin interactive drag session | -| `drag_step(placements)` | no | One mouse-move during drag | -| `post_drag()` | no | End drag session | -| `run_kinematic(ctx)` | no | Run kinematic simulation | -| `num_frames()` | no | Frame count after simulation | -| `update_for_frame(i)` | no | Retrieve frame placements | -| `diagnose(ctx)` | no | Detect redundant/conflicting constraints | -| `is_deterministic()` | no | Whether output is reproducible (default: true) | -| `export_native(path)` | no | Write solver-native debug file (e.g. ASMT) | -| `supports_bundle_fixed()` | no | Whether solver handles Fixed-joint bundling internally | - -## Core types - -All types live in `Types.h` with no FreeCAD dependencies, making the header standalone for future server/worker use. - -**Transform** -- position `[x, y, z]` + unit quaternion `[w, x, y, z]`. Equivalent to `Base::Placement` but independent. Note the quaternion convention differs from `Base::Rotation` which uses `(x, y, z, w)` ordering; the adapter layer handles the swap. - -**BaseJointKind** -- 24 primitive constraint types decomposed from FreeCAD's `JointType` and `DistanceType` enums. Covers point constraints (Coincident, PointOnLine, PointInPlane), axis/surface constraints (Concentric, Tangent, Planar), kinematic joints (Fixed, Revolute, Cylindrical, Slider, Ball, Screw, Universal), mechanical elements (Gear, RackPinion), distance variants, and a `Custom` extension point. - -**SolveContext** -- complete solver input: parts (with placements, mass, grounded flag), constraints (with markers, parameters, limits), optional motion definitions and simulation parameters. - -**SolveResult** -- solver output: status code, updated part placements, DOF count, constraint diagnostics, and simulation frame count. - -## SolverRegistry - -Thread-safe singleton managing solver backends: - -```cpp -auto& reg = SolverRegistry::instance(); - -// Registration (at module init) -reg.register_solver("ondsel", []() { - return std::make_unique(); -}); - -// Retrieval -auto solver = reg.get(); // default solver -auto solver = reg.get("ondsel"); // by name - -// Queries -reg.available(); // ["ondsel", ...] -reg.joints_for("ondsel"); // [Fixed, Revolute, ...] -reg.set_default("ondsel"); -``` - -Plugin discovery scans directories for shared libraries exporting `kcsolve_api_version()` and `kcsolve_create()`. Default paths: `KCSOLVE_PLUGIN_PATH` env var and `/lib/kcsolve/`. - -## OndselAdapter - -The built-in solver backend wrapping OndselSolver's Lagrangian constraint formulation. Registered as `"ondsel"` at Assembly module initialization. - -Supports all 24 joint types. The adapter translates between `SolveContext`/`SolveResult` and OndselSolver's internal `ASMTAssembly` representation, including: - -- Part placement conversion (Transform <-> Base::Placement quaternion ordering) -- Constraint parameter mapping (BaseJointKind -> OndselSolver joint classes) -- Interactive drag protocol (pre_drag/drag_step/post_drag) -- Kinematic simulation (run_kinematic/num_frames/update_for_frame) -- Constraint diagnostics (redundancy detection via MbD system) - -## Python bindings (kcsolve module) - -The `kcsolve` pybind11 module exposes the full C++ API to Python. See [KCSolve Python API](../reference/kcsolve-python.md) for details. - -Key capabilities: -- All enums, structs, and classes accessible from Python -- Subclass `IKCSolver` in pure Python to create new solver backends -- Register Python solvers at runtime via `kcsolve.register_solver()` -- Query the registry from the FreeCAD console - -## File layout - -``` -src/Mod/Assembly/Solver/ -├── Types.h # Enums and structs (no FreeCAD deps) -├── IKCSolver.h # Abstract solver interface -├── SolverRegistry.h/cpp # Singleton registry + plugin loading -├── OndselAdapter.h/cpp # OndselSolver wrapper -├── KCSolveGlobal.h # DLL export macros -├── CMakeLists.txt # Builds libKCSolve.so -└── bindings/ - ├── PyIKCSolver.h # pybind11 trampoline for Python subclasses - ├── kcsolve_py.cpp # Module definition (enums, structs, classes) - └── CMakeLists.txt # Builds kcsolve.so (pybind11 module) -``` - -## Testing - -- **18 C++ tests** (`KCSolve_tests_run`) covering SolverRegistry (8 tests) and OndselAdapter (10 tests including drag protocol and redundancy diagnosis) -- **16 Python tests** (`TestKCSolvePy`) covering module import, type bindings, registry functions, Python solver subclassing, and full register/load/solve round-trips -- **6 Python integration tests** (`TestSolverIntegration`) testing solver behavior through FreeCAD document objects - -## Related - -- [KCSolve Python API Reference](../reference/kcsolve-python.md) -- [INTER_SOLVER.md](../../INTER_SOLVER.md) -- full architecture specification diff --git a/docs/src/architecture/overview.md b/docs/src/architecture/overview.md deleted file mode 100644 index b697dbc7b7..0000000000 --- a/docs/src/architecture/overview.md +++ /dev/null @@ -1,78 +0,0 @@ -# Architecture Overview - -Kindred Create is structured as a thin integration layer on top of FreeCAD. The design follows three principles: - -1. **Minimal core modifications** — prefer submodule addons over patching FreeCAD internals -2. **Graceful degradation** — Create runs without ztools or Silo if submodules are missing -3. **Pure Python addons** — workbenches follow FreeCAD's standard addon pattern - -## Three-layer model - -``` -┌─────────────────────────────────┐ -│ FreeCAD Documents (.FCStd) │ Python source of truth -│ Workbench logic (Python) │ -├─────────────────────────────────┤ -│ PostgreSQL │ Silo metadata, revisions, BOM -├─────────────────────────────────┤ -│ MinIO (S3-compatible) │ Binary file storage cache -└─────────────────────────────────┘ -``` - -FreeCAD documents are the authoritative representation of CAD data. Silo's PostgreSQL database stores metadata (part numbers, revisions, BOM relationships) and MinIO stores the binary `.FCStd` files. The FreeCAD workbench synchronizes between local files and the server. - -## Source layout - -``` -create/ -├── src/App/ # Core application (C++) -├── src/Base/ # Foundation classes (C++) -├── src/Gui/ # GUI framework (C++ + Qt6 + QSS) -│ ├── Stylesheets/ # KindredCreate.qss theme -│ ├── PreferencePacks/ # Theme preference pack -│ ├── Icons/ # silo-*.svg origin icons -│ ├── FileOrigin.* # Abstract file origin interface -│ └── OriginManager.* # Origin lifecycle management -├── src/Mod/ # ~37 FreeCAD modules -│ ├── Create/ # Kindred bootstrap module -│ ├── Assembly/ # Assembly workbench (Kindred patches) -│ ├── PartDesign/ # Part Design (stock + ztools injection) -│ └── ... # Other stock FreeCAD modules -├── mods/ -│ ├── ztools/ # Datum/pattern/pocket workbench (submodule) -│ └── silo/ # Parts database workbench (submodule) -└── src/3rdParty/ - ├── OndselSolver/ # Assembly solver (submodule) - └── GSL/ # Guidelines Support Library (submodule) -``` - -## Bootstrap sequence - -1. FreeCAD core initializes, discovers `src/Mod/Create/` -2. `Init.py` runs `setup_kindred_addons()` — adds `mods/ztools/ztools` and `mods/silo/freecad` to `sys.path`, executes their `Init.py` -3. GUI phase: `InitGui.py` runs `setup_kindred_workbenches()` — executes addon `InitGui.py` files to register workbenches -4. Deferred QTimer cascade: - - **1500ms** — Register Silo as a file origin - - **2000ms** — Dock the Silo auth panel - - **3000ms** — Check for Silo first-start configuration - - **4000ms** — Dock the Silo activity panel - - **10000ms** — Check for application updates - -The QTimer cascade exists because FreeCAD's startup is not fully synchronous — Silo registration must wait for the GUI framework to be ready. - -## Origin system - -The origin system is Kindred's primary addition to FreeCAD's GUI layer: - -- **`FileOrigin`** — abstract C++ interface for file backends -- **`LocalFileOrigin`** — default implementation (local filesystem) -- **`SiloOrigin`** — Silo database backend (registered by the Python addon) -- **`OriginManager`** — manages origin lifecycle, switching, capability queries -- **`OriginSelectorWidget`** — dropdown in the File toolbar -- **`CommandOrigin.cpp`** — Commit / Pull / Push / Info / BOM commands that delegate to the active origin - -## Module interaction - -- **ztools** injects commands into PartDesign via `_ZToolsPartDesignManipulator` -- **Silo** registers as a `FileOrigin` backend via `silo_origin.register_silo_origin()` -- **Create module** is glue only — no feature code, just bootstrap and version management diff --git a/docs/src/architecture/python-source-of-truth.md b/docs/src/architecture/python-source-of-truth.md deleted file mode 100644 index fe9920a1da..0000000000 --- a/docs/src/architecture/python-source-of-truth.md +++ /dev/null @@ -1,26 +0,0 @@ -# Python as Source of Truth - -In Kindred Create's architecture, FreeCAD documents (`.FCStd` files) are the authoritative representation of all CAD data. The Silo database and MinIO storage are caches and metadata indexes — they do not define the model. - -## How it works - -An `.FCStd` file is a ZIP archive containing: -- XML documents describing the parametric model tree -- BREP geometry files for each shape -- Thumbnail images -- Embedded spreadsheets and metadata - -When a user runs **Commit**, the workbench uploads the entire `.FCStd` file to MinIO and records metadata (part number, revision, timestamp, commit message) in PostgreSQL. When a user runs **Pull**, the workbench downloads the `.FCStd` from MinIO and opens it locally. - -## Why this design - -- **No data loss** — the complete model is always in the `.FCStd` file, never split across systems -- **Offline capability** — engineers can work without a server connection -- **FreeCAD compatibility** — files are standard FreeCAD documents, openable in stock FreeCAD -- **Simple sync model** — the unit of transfer is always a whole file, avoiding merge conflicts in binary geometry - -## Trade-offs - -- **Large files** — `.FCStd` files can grow large with complex assemblies; every commit stores the full file -- **No partial sync** — you cannot pull a single feature or component; it is all or nothing -- **Conflict resolution** — two users editing the same file must resolve conflicts manually (last-commit-wins by default) diff --git a/docs/src/architecture/signal-architecture.md b/docs/src/architecture/signal-architecture.md deleted file mode 100644 index dbc0db4dd0..0000000000 --- a/docs/src/architecture/signal-architecture.md +++ /dev/null @@ -1,274 +0,0 @@ -# Signal Architecture - -Kindred Create uses two signal systems side by side: **fastsignals** for domain and application events, and **Qt signals** for UI framework integration. This page explains why both exist, where each is used, and the patterns for working with them. - -## Why two signal systems - -Qt signals require the `Q_OBJECT` macro, MOC preprocessing, and a `QObject` base class. This makes them the right tool for widget-to-widget communication but a poor fit for domain classes like `FileOrigin`, `OriginManager`, and `App::Document` that are not `QObject` subclasses and should not depend on the Qt meta-object system. - -fastsignals is a header-only C++ library that provides type-safe signals with lambda support, RAII connection management, and no build-tool preprocessing. It lives at `src/3rdParty/FastSignals/` and is linked into the Gui module via CMake. - -| | Qt signals | fastsignals | -|---|-----------|-------------| -| **Requires** | `Q_OBJECT`, MOC, `QObject` base | Nothing (header-only) | -| **Dispatch** | Event loop (can be queued) | Synchronous, immediate | -| **Connection** | `QObject::connect()` | `signal.connect(lambda)` | -| **Lifetime** | Tied to `QObject` parent-child tree | `scoped_connection` RAII | -| **Thread model** | Can queue across threads | Fires on emitter's thread | -| **Slot types** | Slots, lambdas, `std::function` | Lambdas, `std::function`, function pointers | - -## Where each is used - -### fastsignals — domain events - -These are events about application state that multiple independent listeners may care about. The emitting class is not a `QObject`. - -**FileOrigin** (`src/Gui/FileOrigin.h`): - -```cpp -fastsignals::signal signalConnectionStateChanged; -``` - -**OriginManager** (`src/Gui/OriginManager.h`): - -```cpp -fastsignals::signal signalOriginRegistered; -fastsignals::signal signalOriginUnregistered; -fastsignals::signal signalCurrentOriginChanged; -fastsignals::signal signalDocumentOriginChanged; -``` - -**Gui::Document** (`src/Gui/Document.h`): - -```cpp -mutable fastsignals::signal signalNewObject; -mutable fastsignals::signal signalDeletedObject; -mutable fastsignals::signal signalChangedObject; -// ~9 more document-level signals -``` - -**Gui::Application** (`src/Gui/Application.h`): - -```cpp -fastsignals::signal signalNewDocument; -fastsignals::signal signalDeleteDocument; -fastsignals::signal signalNewObject; -// ~7 more application-level signals -``` - -### Qt signals — UI interaction - -These are events between Qt widgets, actions, and framework components where `QObject` is already the base class and the Qt event loop handles dispatch. - -**OriginSelectorWidget** (a `QToolButton` subclass): - -```cpp -// QActionGroup::triggered → onOriginActionTriggered -connect(m_originActions, &QActionGroup::triggered, - this, &OriginSelectorWidget::onOriginActionTriggered); - -// QAction::triggered → onManageOriginsClicked -connect(m_manageAction, &QAction::triggered, - this, &OriginSelectorWidget::onManageOriginsClicked); -``` - -### The boundary - -The pattern is consistent: **fastsignals for observable state changes in domain classes, Qt signals for UI framework plumbing**. A typical flow crosses the boundary once: - -``` -OriginManager emits signalCurrentOriginChanged (fastsignals) - → OriginSelectorWidget::onCurrentOriginChanged (lambda listener) - → updates QToolButton text/icon (Qt API calls) -``` - -## fastsignals API - -### Declaring a signal - -Signals are public member variables with a template signature: - -```cpp -fastsignals::signal signalSomethingHappened; -``` - -Use `mutable` if the signal needs to fire from `const` methods (as `Gui::Document` does): - -```cpp -mutable fastsignals::signal signalReadOnlyEvent; -``` - -Name signals with the `signal` prefix by convention. - -### Emitting - -Call the signal like a function: - -```cpp -signalSomethingHappened("hello"); -``` - -All connected slots execute **synchronously**, in connection order, before the call returns. There is no event loop queuing. - -### Connecting - -`signal.connect()` accepts any callable and returns a connection object: - -```cpp -auto conn = mySignal.connect([](const std::string& s) { - Base::Console().log("Got: %s\n", s.c_str()); -}); -``` - -### Connection types - -| Type | RAII | Copyable | Use case | -|------|------|----------|----------| -| `fastsignals::connection` | No | Yes | Long-lived, manually managed | -| `fastsignals::scoped_connection` | Yes | No (move only) | Member variable, automatic cleanup | -| `fastsignals::advanced_connection` | No | No | Temporary blocking via `shared_connection_block` | - -`scoped_connection` is the standard choice for class members. It disconnects automatically when destroyed. - -### Disconnecting - -```cpp -conn.disconnect(); // Explicit -// or let scoped_connection destructor handle it -``` - -## Connection patterns - -### Pattern 1: Scoped member connections - -The most common pattern. Store `scoped_connection` as a class member and connect in the constructor or an `attach()` method. - -```cpp -class MyListener { - fastsignals::scoped_connection m_conn; - -public: - MyListener(OriginManager* mgr) - { - m_conn = mgr->signalOriginRegistered.connect( - [this](const std::string& id) { onRegistered(id); } - ); - } - - // Destructor auto-disconnects via m_conn - ~MyListener() = default; - -private: - void onRegistered(const std::string& id) { /* ... */ } -}; -``` - -### Pattern 2: Explicit disconnect in destructor - -`OriginSelectorWidget` disconnects explicitly before destruction to prevent any signal delivery during teardown: - -```cpp -OriginSelectorWidget::~OriginSelectorWidget() -{ - disconnectSignals(); // m_conn*.disconnect() -} -``` - -This is defensive — `scoped_connection` would disconnect on its own, but explicit disconnection avoids edge cases where a signal fires between member destruction order. - -### Pattern 3: DocumentObserver base class - -`Gui::DocumentObserver` wraps ~10 document signals behind virtual methods: - -```cpp -class DocumentObserver { - using Connection = fastsignals::scoped_connection; - Connection connectDocumentCreatedObject; - Connection connectDocumentDeletedObject; - // ... - - void attachDocument(Document* doc); // connects all - void detachDocument(); // disconnects all -}; -``` - -Subclasses override `slotCreatedObject()`, `slotDeletedObject()`, etc. This pattern avoids repeating connection boilerplate in every document listener. - -### Pattern 4: Temporary blocking - -`advanced_connection` supports blocking a slot without disconnecting: - -```cpp -auto conn = signal.connect(handler, fastsignals::advanced_tag()); -fastsignals::shared_connection_block block(conn, true); // blocked - -signal(); // handler does NOT execute - -block.unblock(); -signal(); // handler executes -``` - -Use this to prevent recursive signal handling. - -## Thread safety - -### What is thread-safe - -- **Emitting** a signal from any thread (internal spin mutex protects the slot list). -- **Connecting and disconnecting** from any thread, even while slots are executing on another thread. - -### What is not thread-safe - -- **Accessing the same `connection` object** from multiple threads. Protect with your own mutex or keep connection objects thread-local. -- **Slot execution context.** Slots run on the emitter's thread. If a fastsignal fires on a background thread and the slot touches Qt widgets, you must marshal to the main thread: - -```cpp -mgr->signalOriginRegistered.connect([this](const std::string& id) { - QMetaObject::invokeMethod(this, [this, id]() { - // Now on the main thread — safe to update UI - updateUI(id); - }, Qt::QueuedConnection); -}); -``` - -In practice, all origin and document signals in Kindred Create fire on the main thread, so this marshalling is not currently needed. It would become necessary if background workers emitted signals. - -## Performance - -- **Emission:** O(n) where n = connected slots. No allocation, no event loop overhead. -- **Connection:** O(1) with spin mutex. -- **Memory:** Each signal stores a shared pointer to a slot vector. Each `scoped_connection` is ~16 bytes. -- fastsignals is rarely a bottleneck. Profile before optimising signal infrastructure. - -## Adding a new signal - -1. Declare the signal as a public member (or `mutable` if emitting from const methods). -2. Name it with the `signal` prefix. -3. Emit it at the appropriate point in your code. -4. Listeners store `scoped_connection` members and connect via lambdas. -5. Document the signal's signature and when it fires. - -Do not create a fastsignal for single-listener scenarios — a direct method call is simpler. - -## Common mistakes - -**Dangling `this` capture.** If a lambda captures `this` and the object is destroyed before the connection, the next emission crashes. Always store the connection as a `scoped_connection` member so it disconnects on destruction. - -**Assuming queued dispatch.** fastsignals are synchronous. A slot that blocks will block the emitter. Keep slots fast or offload work to a background thread. - -**Forgetting `mutable`.** If you need to emit from a `const` method, the signal member must be `mutable`. Otherwise the compiler rejects the call. - -**Copying `scoped_connection`.** It is move-only. Use `std::move()` when putting connections into containers: - -```cpp -std::vector conns; -conns.push_back(std::move(conn)); // OK -``` - -## See also - -- [OriginManager](../reference/cpp-origin-manager.md) — signal catalog for origin lifecycle events -- [FileOrigin Interface](../reference/cpp-file-origin.md) — `signalConnectionStateChanged` -- [OriginSelectorWidget](../reference/cpp-origin-selector-widget.md) — listener patterns in practice -- `src/3rdParty/FastSignals/` — library source and headers diff --git a/docs/src/architecture/silo-server.md b/docs/src/architecture/silo-server.md deleted file mode 100644 index 3b787f0bb4..0000000000 --- a/docs/src/architecture/silo-server.md +++ /dev/null @@ -1,11 +0,0 @@ -# Silo Server - -The Silo server architecture is documented in the dedicated [Silo Server](../silo-server/overview.md) section. - -- [Overview](../silo-server/overview.md) — Architecture, stack, and key features -- [Specification](../silo-server/SPECIFICATION.md) — Full API specification with 38+ routes -- [Configuration](../silo-server/CONFIGURATION.md) — YAML config reference -- [Deployment](../silo-server/DEPLOYMENT.md) — Docker Compose, systemd, production setup -- [Authentication](../silo-server/AUTH.md) — LDAP, OIDC, and local auth backends -- [Status System](../silo-server/STATUS.md) — Revision lifecycle states -- [Gap Analysis](../silo-server/GAP_ANALYSIS.md) — Current gaps and planned improvements diff --git a/docs/src/quicknav/SPEC.md b/docs/src/quicknav/SPEC.md deleted file mode 100644 index 04f3260e3e..0000000000 --- a/docs/src/quicknav/SPEC.md +++ /dev/null @@ -1,441 +0,0 @@ -# QuickNav — Keyboard Navigation Addon Specification - -**Addon name:** QuickNav -**Type:** Pure Python FreeCAD addon (no C++ required) -**Compatibility:** FreeCAD 1.0+, Kindred Create 0.1+ -**Location:** `mods/quicknav/` - ---- - -## 1. Overview - -QuickNav provides keyboard-driven command access for FreeCAD and Kindred Create. It replaces mouse-heavy toolbar navigation with a numbered key system organized by workbench and command grouping. The addon is activated by loading its workbench and toggled on/off with the `0` key. - -### Design Goals - -- Numbers `1-9` execute commands within the active command grouping -- `Shift+1-9` switches command grouping within the active workbench -- `Ctrl+1-9` switches workbench context -- All groupings and workbenches are ordered by most-recently-used (MRU) history -- History is unlimited internally, top 9 shown, remainder scrollable/clickable -- Mouse interaction remains fully functional — QuickNav is purely additive -- Configuration persisted via `FreeCAD.ParamGet()` - ---- - -## 2. Terminology - -| Term | Definition | -|------|-----------| -| **Workbench** | A FreeCAD workbench (Sketcher, PartDesign, Assembly, etc.). Fixed assignment to Ctrl+N slots. | -| **Command Grouping** | A logical group of commands within a workbench, mapped from existing FreeCAD toolbar groupings. Max 9 per tier. | -| **Active Grouping** | The left-most visible grouping in the navigation bar. Its commands are accessible via `1-9`. | -| **Navigation Bar** | Bottom toolbar displaying the current state: active workbench, groupings, and numbered commands. | -| **MRU Stack** | Most-recently-used ordering. Position 0 = currently active, 1 = previously active, etc. | -| **Tier** | When a workbench has >9 command groupings, they are split: Tier 1 (most common 9), Tier 2 (next 9). | - ---- - -## 3. Key Bindings - -### 3.1 Mode Toggle - -| Key | Action | -|-----|--------| -| `0` | Toggle QuickNav on/off. When off, all QuickNav key interception is disabled and the navigation bar hides. | - -### 3.2 Command Execution - -| Key | Action | -|-----|--------| -| `1-9` | Execute the Nth command in the active grouping. If the command is auto-executable (e.g., Pad after closed sketch), execute immediately. Otherwise, enter tool mode (same as clicking the toolbar button). | - -### 3.3 Grouping Navigation - -| Key | Action | -|-----|--------| -| `Shift+1-9` | Switch to the Nth command grouping (MRU ordered) within the current workbench. The newly activated grouping moves to position 0 in the MRU stack. | -| `Shift+Left/Right` | Scroll through groupings beyond the visible 9. | - -### 3.4 Workbench Navigation - -| Key | Action | -|-----|--------| -| `Ctrl+1` | Sketcher | -| `Ctrl+2` | Part Design | -| `Ctrl+3` | Assembly | -| `Ctrl+4` | Spreadsheet | -| `Ctrl+5` | TechDraw | -| `Ctrl+6-9` | User-configurable / additional workbenches | - -Switching workbench via `Ctrl+N` also restores that workbench's last-active command grouping. - ---- - -## 4. Navigation Bar - -The navigation bar is a `QToolBar` positioned at the bottom of the main window (replacing or sitting alongside FreeCAD's default bottom toolbar area). - -### 4.1 Layout - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ [WB: Sketcher] │ ❶ Primitives │ ② Constraints │ ③ Dimensions │ ◀▶ │ -│ │ 1:Line 2:Rect 3:Circle 4:Arc 5:Point 6:Slot ... │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -- **Left section:** Current workbench name with Ctrl+N hint -- **Middle section (top row):** Command groupings, MRU ordered. Active grouping is ❶ (filled circle), others are ②③ etc. Scrollable horizontally if >9. -- **Middle section (bottom row):** Commands within the active grouping, numbered 1-9 -- **Right section:** Scroll arrows for overflow groupings - -### 4.2 Visual States - -- **Active grouping:** Bold text, filled number badge, Catppuccin Mocha `blue` (#89b4fa) accent -- **Inactive groupings:** Normal text, outlined number badge, `surface1` (#45475a) text -- **Hovered command:** `surface2` (#585b70) background highlight -- **Active command (tool in use):** `green` (#a6e3a1) underline indicator - -### 4.3 Mouse Interaction - -- Click any grouping to activate it (equivalent to Shift+N) -- Click any command to execute it (equivalent to pressing N) -- Scroll wheel on grouping area to cycle through overflow groupings -- Click scroll arrows to page through overflow - ---- - -## 5. Workbench Command Groupings - -Each workbench's existing FreeCAD toolbars map to command groupings. Where a workbench has >9 toolbars, split into Tier 1 (default, most common) and Tier 2 (accessible via scrolling or `Shift+Left/Right`). - -### 5.1 Sketcher (Ctrl+1) - -| Grouping | Commands (1-9) | -|----------|---------------| -| Primitives | Line, Rectangle, Circle, Arc, Point, Slot, B-Spline, Polyline, Ellipse | -| Constraints | Coincident, Horizontal, Vertical, Parallel, Perpendicular, Tangent, Equal, Symmetric, Block | -| Dimensions | Distance, Horizontal Distance, Vertical Distance, Radius, Diameter, Angle, Lock, Constrain Refraction | -| Construction | Toggle Construction, External Geometry, Carbon Copy, Offset, Trim, Extend, Split | -| Tools | Mirror, Array (Linear), Array (Polar), Move, Rotate, Scale, Close Shape, Connect Edges | - -### 5.2 Part Design (Ctrl+2) - -| Grouping | Commands (1-9) | -|----------|---------------| -| Additive | Pad, Revolution, Additive Loft, Additive Pipe, Additive Helix, Additive Box, Additive Cylinder, Additive Sphere, Additive Cone | -| Subtractive | Pocket, Hole, Groove, Subtractive Loft, Subtractive Pipe, Subtractive Helix, Subtractive Box, Subtractive Cylinder, Subtractive Sphere | -| Datums | New Sketch, Datum Plane, Datum Line, Datum Point, Shape Binder, Sub-Shape Binder, ZTools Datum Creator, ZTools Datum Manager | -| Transformations | Mirrored, Linear Pattern, Polar Pattern, MultiTransform, ZTools Rotated Linear Pattern | -| Modeling | Fillet, Chamfer, Draft, Thickness, Boolean, ZTools Enhanced Pocket | - -### 5.3 Assembly (Ctrl+3) - -| Grouping | Commands (1-9) | -|----------|---------------| -| Components | Insert Component, Create Part, Create Assembly, Ground, BOM | -| Joints | Fixed, Revolute, Cylindrical, Slider, Ball, Planar, Distance, Angle, Parallel | -| Patterns | ZTools Linear Pattern, ZTools Polar Pattern | - -### 5.4 Spreadsheet (Ctrl+4) - -| Grouping | Commands (1-9) | -|----------|---------------| -| Editing | Merge Cells, Split Cell, Alias, Import CSV, Export CSV | -| Formatting | Bold, Italic, Underline, Align Left, Align Center, Align Right, BG Color, Text Color, Quick Alias | - -### 5.5 TechDraw (Ctrl+5) - -Groupings derived from TechDraw's existing toolbars at runtime. - -> **Note:** The exact command lists above are initial defaults. The addon discovers available commands from each workbench's toolbar structure at activation time and falls back to these defaults only if discovery fails. - ---- - -## 6. MRU History Behavior - -### 6.1 Grouping History (per workbench) - -Each workbench maintains its own grouping MRU stack. - -- When a grouping is activated (via `Shift+N` or mouse click), it moves to position 0 -- The previously active grouping moves to position 1, everything else shifts down -- Position 0 is always the active grouping (already selected, shown leftmost) -- `Shift+1` is a no-op (already active), `Shift+2` activates the previous grouping, etc. - -### 6.2 Workbench History - -- Workbenches have fixed Ctrl+N assignments (not MRU ordered) -- However, each workbench remembers its last-active grouping -- Switching to a workbench restores its last-active grouping as position 0 - -### 6.3 Persistence - -Stored in `FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/QuickNav")`: - -| Parameter | Type | Description | -|-----------|------|-------------| -| `Enabled` | Bool | Whether QuickNav is currently active | -| `GroupHistory/` | String | Semicolon-delimited list of grouping names in MRU order | -| `LastGrouping/` | String | Name of the last-active grouping per workbench | -| `CustomSlots/Ctrl6` through `Ctrl9` | String | Workbench names for user-configurable slots | - ---- - -## 7. Auto-Execution Logic - -When a command is invoked via number key, QuickNav checks if the command can be auto-executed: - -### 7.1 Auto-Execute Conditions - -A command auto-executes (runs and completes without entering a persistent mode) when: - -1. **Pad/Pocket after closed sketch:** If the active body has a sketch that was just closed (sketch edit mode exited with a closed profile), pressing the Pad or Pocket command key creates the feature with default parameters. The task panel still opens for parameter adjustment. -2. **Boolean operations:** If exactly two bodies/shapes are selected, boolean commands execute with defaults. -3. **Constraint application:** If appropriate geometry is pre-selected in Sketcher, constraint commands apply immediately. - -### 7.2 Mode-Entry (Default) - -All other commands enter their standard FreeCAD tool mode — identical to clicking the toolbar button. The user interacts with the 3D view and/or task panel as normal. - ---- - -## 8. Key Event Handling - -### 8.1 Event Filter Architecture - -```python -class QuickNavEventFilter(QObject): - """Installed on FreeCAD's main window via installEventFilter(). - - Intercepts KeyPress events when QuickNav is active. - Passes through all events when QuickNav is inactive. - """ - - def eventFilter(self, obj, event): - if event.type() != QEvent.KeyPress: - return False - if not self._active: - return False - - # Don't intercept when a text input widget has focus - focused = QApplication.focusWidget() - if isinstance(focused, (QLineEdit, QTextEdit, QPlainTextEdit, QSpinBox, QDoubleSpinBox)): - return False - - # Don't intercept when task panel input fields are focused - if self._is_task_panel_input(focused): - return False - - key = event.key() - modifiers = event.modifiers() - - if key == Qt.Key_0 and modifiers == Qt.NoModifier: - self.toggle_active() - return True - - if key >= Qt.Key_1 and key <= Qt.Key_9: - n = key - Qt.Key_0 - if modifiers == Qt.ControlModifier: - self.switch_workbench(n) - return True - elif modifiers == Qt.ShiftModifier: - self.switch_grouping(n) - return True - elif modifiers == Qt.NoModifier: - self.execute_command(n) - return True - - return False # Pass through all other keys -``` - -### 8.2 Conflict Resolution - -QuickNav's event filter takes priority when active. FreeCAD's existing keybindings for `Ctrl+1` through `Ctrl+9` (if any) are overridden while QuickNav is enabled. The original bindings are restored when QuickNav is toggled off or unloaded. - -Existing `Shift+` and bare number key bindings in FreeCAD are similarly overridden only while QuickNav is active. This is safe because: -- FreeCAD does not use bare number keys as shortcuts by default -- Shift+number is not commonly bound in default FreeCAD - -### 8.3 Input Widget Safety - -The event filter must NOT intercept keys when the user is: -- Typing in the Python console -- Entering values in the task panel (dimensions, parameters) -- Editing spreadsheet cells -- Typing in any `QLineEdit`, `QTextEdit`, `QSpinBox`, or `QDoubleSpinBox` -- Using the Sketcher's inline dimension input - ---- - -## 9. Addon Structure - -``` -mods/quicknav/ -├── package.xml # FreeCAD addon manifest with extension -├── Init.py # Non-GUI initialization (no-op) -├── InitGui.py # Registers QuickNavWorkbench -├── quicknav/ -│ ├── __init__.py -│ ├── core.py # QuickNavManager singleton — orchestrates state -│ ├── event_filter.py # QuickNavEventFilter (QObject) -│ ├── nav_bar.py # NavigationBar (QToolBar subclass) -│ ├── workbench_map.py # Fixed workbench → Ctrl+N mapping + grouping discovery -│ ├── history.py # MRU stack with ParamGet persistence -│ ├── auto_exec.py # Auto-execution condition checks -│ ├── commands.py # FreeCAD command wrappers (QuickNav_Toggle, etc.) -│ └── resources/ -│ ├── icons/ # Number badge SVGs, QuickNav icon -│ └── theme.py # Catppuccin Mocha color tokens -└── tests/ - └── test_history.py # MRU stack unit tests -``` - -### 9.1 Manifest - -```xml - - - QuickNav - Keyboard-driven toolbar navigation - 0.1.0 - Kindred Systems - LGPL-2.1 - - - QuickNavWorkbench - - - - 0.1.0 - 10 - true - - sdk - - - -``` - -### 9.2 Activation - -QuickNav activates when its workbench is loaded (via the addon loader or manual activation). It installs the event filter on the main window and creates the navigation bar. The workbench itself is invisible — it does not add its own toolbars or menus beyond the navigation bar. It acts as a transparent overlay on whatever workbench the user is actually working in. - -```python -class QuickNavWorkbench(Gui.Workbench): - """Invisible workbench that installs QuickNav on load. - - QuickNav doesn't replace the active workbench — it layers on top. - Loading QuickNav installs the event filter and nav bar, then - immediately re-activates the previously active workbench. - """ - - def Initialize(self): - QuickNavManager.instance().install() - - def Activated(self): - # Re-activate the previous workbench so QuickNav is transparent - prev = QuickNavManager.instance().previous_workbench - if prev: - Gui.activateWorkbench(prev) - - def Deactivated(self): - pass - - def GetClassName(self): - return "Gui::PythonWorkbench" -``` - -**Alternative (preferred for Create):** Instead of a workbench, QuickNav can be activated directly from `Create/InitGui.py` at boot, gated by the `Enabled` preference. This avoids the workbench-switching dance entirely. The `QuickNavWorkbench` registration is kept for standalone FreeCAD compatibility. - ---- - -## 10. Command Discovery - -At activation time, QuickNav introspects each workbench's toolbars to build the command grouping map. - -```python -def discover_groupings(workbench_name: str) -> list[CommandGrouping]: - """Discover command groupings from a workbench's toolbar structure. - - 1. Temporarily activate the workbench (if not already active) - 2. Enumerate QToolBars from the main window - 3. Map toolbar name → list of QAction names - 4. Filter out non-command actions (separators, widgets) - 5. Split into tiers if >9 groupings - 6. Restore the previously active workbench - """ -``` - -### 10.1 Fallback Defaults - -If toolbar discovery fails (workbench not initialized, empty toolbars), QuickNav falls back to the hardcoded groupings in Section 5. These are stored as a Python dict in `workbench_map.py`. - -### 10.2 ZTools Integration - -ZTools commands injected via `WorkbenchManipulator` appear in the discovered toolbars and are automatically included in the relevant groupings. No special handling is needed — QuickNav discovers commands after all manipulators have run. - ---- - -## 11. FreeCAD Compatibility - -QuickNav is designed as a standalone FreeCAD addon that works without Kindred Create or the SDK. - -| Feature | FreeCAD | Kindred Create | -|---------|---------|----------------| -| Core navigation (keys, nav bar) | ✅ | ✅ | -| Catppuccin Mocha theming | ❌ (uses Qt defaults) | ✅ (via SDK theme tokens) | -| Auto-boot on startup | ❌ (manual workbench activation) | ✅ (via addon loader) | -| ZTools commands in groupings | ❌ (not present) | ✅ (discovered from manipulated toolbars) | - -The SDK dependency is optional — QuickNav checks for `kindred_sdk` availability and degrades gracefully: - -```python -try: - from kindred_sdk.theme import get_theme_tokens - THEME = get_theme_tokens() -except ImportError: - THEME = None # Use Qt default palette -``` - ---- - -## 12. Implementation Phases - -### Phase 1: Core Infrastructure -- Event filter with key interception and input widget safety -- QuickNavManager singleton with toggle on/off -- Navigation bar widget (QToolBar) with basic layout -- Hardcoded workbench/grouping maps from Section 5 -- ParamGet persistence for enabled state - -### Phase 2: Dynamic Discovery -- Toolbar introspection for command grouping discovery -- MRU history with persistence -- Grouping overflow scrolling -- Workbench restore (last-active grouping per workbench) - -### Phase 3: Auto-Execution -- Context-aware auto-execute logic -- Sketcher closed-profile detection for Pad/Pocket -- Pre-selection constraint application - -### Phase 4: Polish -- Number badge SVG icons -- Catppuccin Mocha theming (conditional on SDK) -- Scroll animations -- Settings dialog (custom Ctrl+6-9 assignments) -- FreeCAD standalone packaging - ---- - -## 13. Open Questions - -1. **Tier switching UX:** When a workbench has >9 groupings split into tiers, should `Shift+0` toggle between tiers, or should tiers be purely a scroll/mouse concept? - -2. **Visual number badges:** Should the commands in the nav bar show keycap-style badges (like `⌨ 1`) or just prepend the number (`1: Line`)? - -3. **Sketcher inline dimension input:** FreeCAD's Sketcher has an inline dimension entry that isn't a standard QLineEdit. Need to verify the event filter correctly identifies and skips this widget. - -4. **Ctrl+N conflicts with Create shortcuts:** Verify that Create/Silo don't already bind Ctrl+1 through Ctrl+9. The Silo toggle uses Ctrl+O/S/N, so these should be clear. diff --git a/docs/src/silo-server/CONFIGURATION.md b/docs/src/silo-server/CONFIGURATION.md index a7b0192368..37bb20021b 100644 --- a/docs/src/silo-server/CONFIGURATION.md +++ b/docs/src/silo-server/CONFIGURATION.md @@ -1,6 +1,6 @@ # Configuration Reference -**Last Updated:** 2026-02-06 +**Last Updated:** 2026-03-01 --- @@ -153,6 +153,70 @@ odoo: --- +## Approval Workflows + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `workflows.directory` | string | `"/etc/silo/workflows"` | Path to directory containing YAML workflow definition files | + +Workflow definition files describe multi-stage approval processes using a state machine pattern. Each file defines a workflow with states, transitions, and approver requirements. + +```yaml +workflows: + directory: "/etc/silo/workflows" +``` + +--- + +## Solver + +| Key | Type | Default | Env Override | Description | +|-----|------|---------|-------------|-------------| +| `solver.default_solver` | string | `""` | `SILO_SOLVER_DEFAULT` | Default solver backend name | +| `solver.max_context_size_mb` | int | `10` | — | Maximum SolveContext payload size in MB | +| `solver.default_timeout` | int | `300` | — | Default solver job timeout in seconds | +| `solver.auto_diagnose_on_commit` | bool | `false` | — | Auto-submit diagnose job on assembly revision commit | + +The solver module depends on the `jobs` module being enabled. See [SOLVER.md](SOLVER.md) for the full solver service specification. + +```yaml +solver: + default_solver: "ondsel" + max_context_size_mb: 10 + default_timeout: 300 + auto_diagnose_on_commit: true +``` + +--- + +## Modules + +Optional module toggles. Each module can be explicitly enabled or disabled. If not listed, the module's built-in default applies. See [MODULES.md](MODULES.md) for the full module system specification. + +```yaml +modules: + projects: + enabled: true + audit: + enabled: true + odoo: + enabled: false + freecad: + enabled: true + jobs: + enabled: false + dag: + enabled: false + solver: + enabled: false + sessions: + enabled: true +``` + +The `auth.enabled` field controls the `auth` module directly (not duplicated under `modules:`). The `sessions` module depends on `auth` and is enabled by default. + +--- + ## Authentication Authentication has a master toggle and three independent backends. When `auth.enabled` is `false`, all routes are accessible without login and a synthetic admin user (`dev`) is injected into every request. @@ -271,6 +335,7 @@ All environment variable overrides. These take precedence over values in `config | `SILO_ADMIN_PASSWORD` | `auth.local.default_admin_password` | Default admin password | | `SILO_LDAP_BIND_PASSWORD` | `auth.ldap.bind_password` | LDAP service account password | | `SILO_OIDC_CLIENT_SECRET` | `auth.oidc.client_secret` | OIDC client secret | +| `SILO_SOLVER_DEFAULT` | `solver.default_solver` | Default solver backend name | Additionally, YAML values can reference environment variables directly using `${VAR_NAME}` syntax, which is expanded at load time via `os.ExpandEnv()`. diff --git a/docs/src/silo-server/GAP_ANALYSIS.md b/docs/src/silo-server/GAP_ANALYSIS.md index 4fa8cbdf44..174eb54caf 100644 --- a/docs/src/silo-server/GAP_ANALYSIS.md +++ b/docs/src/silo-server/GAP_ANALYSIS.md @@ -1,6 +1,6 @@ # Silo Gap Analysis -**Date:** 2026-02-13 +**Date:** 2026-03-01 **Status:** Analysis Complete (Updated) --- @@ -130,8 +130,8 @@ FreeCAD workbench maintained in separate [silo-mod](https://git.kindred-systems. |-----|-------------|--------|--------| | ~~**No rollback**~~ | ~~Cannot revert to previous revision~~ | ~~Data recovery difficult~~ | **Implemented** | | ~~**No comparison**~~ | ~~Cannot diff between revisions~~ | ~~Change tracking manual~~ | **Implemented** | -| **No locking** | No concurrent edit protection | Multi-user unsafe | Open | -| **No approval workflow** | No release/sign-off process | Quality control gap | Open | +| **No locking** | No concurrent edit protection | Multi-user unsafe | Partial (edit sessions with hard interference detection; full pessimistic locking not yet implemented) | +| ~~**No approval workflow**~~ | ~~No release/sign-off process~~ | ~~Quality control gap~~ | **Implemented** (YAML-configurable ECO workflows, multi-stage review gates, digital signatures) | ### 3.2 Important Gaps @@ -355,47 +355,54 @@ These design decisions remain unresolved: ## Appendix A: File Structure -Revision endpoints, status, labels, authentication, audit logging, and file attachments are implemented. Current structure: +Current structure: ``` internal/ api/ + approval_handlers.go # Approval/ECO workflow endpoints audit_handlers.go # Audit/completeness endpoints auth_handlers.go # Login, tokens, OIDC bom_handlers.go # Flat BOM, cost roll-up + broker.go # SSE broker with targeted delivery + dag_handlers.go # Dependency DAG endpoints + dependency_handlers.go # .kc dependency resolution file_handlers.go # Presigned uploads, item files, thumbnails - handlers.go # Items, schemas, projects, revisions + handlers.go # Items, schemas, projects, revisions, Server struct + job_handlers.go # Job queue endpoints + location_handlers.go # Location hierarchy endpoints + macro_handlers.go # .kc macro endpoints + metadata_handlers.go # .kc metadata endpoints middleware.go # Auth middleware odoo_handlers.go # Odoo integration endpoints - routes.go # Route registration (78 endpoints) + pack_handlers.go # .kc checkout packing + routes.go # Route registration (~140 endpoints) + runner_handlers.go # Job runner endpoints search.go # Fuzzy search + session_handlers.go # Edit session acquire/release/query + settings_handlers.go # Admin settings endpoints + solver_handlers.go # Solver service endpoints + sse_handler.go # SSE event stream handler + workstation_handlers.go # Workstation registration auth/ auth.go # Auth service: local, LDAP, OIDC db/ + edit_sessions.go # Edit session repository items.go # Item and revision repository item_files.go # File attachment repository - relationships.go # BOM repository + jobs.go # Job queue repository projects.go # Project repository + relationships.go # BOM repository + workstations.go # Workstation repository + modules/ + modules.go # Module registry (12 modules) + loader.go # Config-to-module state loader storage/ storage.go # File storage helpers migrations/ 001_initial.sql # Core schema ... - 011_item_files.sql # Item file attachments (latest) -``` - -Future features would add: - -``` -internal/ - api/ - lock_handlers.go # Locking endpoints - db/ - locks.go # Lock repository - releases.go # Release repository -migrations/ - 012_item_locks.sql # Locking table - 013_releases.sql # Release management + 023_edit_sessions.sql # Edit session tracking (latest) ``` --- @@ -465,28 +472,28 @@ This section compares Silo's capabilities against SOLIDWORKS PDM features. Gaps | Feature | SOLIDWORKS PDM | Silo Status | Priority | Complexity | |---------|---------------|-------------|----------|------------| -| Check-in/check-out | Full pessimistic locking | None | High | Moderate | +| Check-in/check-out | Full pessimistic locking | Partial (edit sessions with hard interference) | High | Moderate | | Version history | Complete with branching | Full (linear) | - | - | | Revision labels | A, B, C or custom schemes | Full (custom labels) | - | - | | Rollback/restore | Full | Full | - | - | | Compare revisions | Visual + metadata diff | Metadata diff only | Medium | Complex | | Get Latest Revision | One-click retrieval | Partial (API only) | Medium | Simple | -Silo lacks pessimistic locking (check-out), which is critical for multi-user CAD environments where file merging is impractical. Visual diff comparison would require FreeCAD integration for CAD file visualization. +Silo has edit sessions with hard interference detection (unique index on item + context_level + object_id prevents two users from editing the same object simultaneously). Full pessimistic file-level locking is not yet implemented. Visual diff comparison would require FreeCAD integration for CAD file visualization. ### C.2 Workflow Management | Feature | SOLIDWORKS PDM | Silo Status | Priority | Complexity | |---------|---------------|-------------|----------|------------| -| Custom workflows | Full visual designer | None | Critical | Complex | -| State transitions | Configurable with permissions | Basic (status field only) | Critical | Complex | -| Parallel approvals | Multiple approvers required | None | High | Complex | +| Custom workflows | Full visual designer | Full (YAML-defined state machines) | - | - | +| State transitions | Configurable with permissions | Full (configurable transition rules) | - | - | +| Parallel approvals | Multiple approvers required | Full (multi-stage review gates) | - | - | | Automatic transitions | Timer/condition-based | None | Medium | Moderate | | Email notifications | On state change | None | High | Moderate | -| ECO process | Built-in change management | None | High | Complex | +| ECO process | Built-in change management | Full (YAML-configurable ECO workflows) | - | - | | Child state conditions | Block parent if children invalid | None | Medium | Moderate | -Workflow management is the largest functional gap. SOLIDWORKS PDM offers sophisticated state machines with parallel approvals, automatic transitions, and deep integration with engineering change processes. Silo currently has only a simple status field (draft/review/released/obsolete) with no transition rules or approval processes. +Workflow management has been significantly addressed. Silo now supports YAML-defined state machine workflows with configurable transitions, multi-stage approval gates, and digital signatures. Remaining gaps: automatic timer-based transitions, email notifications, and child state condition enforcement. ### C.3 User Management & Security @@ -549,13 +556,13 @@ CAD integration is maintained in separate repositories ([silo-mod](https://git.k | Feature | SOLIDWORKS PDM | Silo Status | Priority | Complexity | |---------|---------------|-------------|----------|------------| | ERP integration | SAP, Dynamics, etc. | Partial (Odoo stubs) | Medium | Complex | -| API access | Full COM/REST API | Full REST API (78 endpoints) | - | - | +| API access | Full COM/REST API | Full REST API (~140 endpoints) | - | - | | Dispatch scripts | Automation without coding | None | Medium | Moderate | -| Task scheduler | Background processing | None | Medium | Moderate | +| Task scheduler | Background processing | Full (job queue with runners) | - | - | | Email system | SMTP integration | None | High | Simple | | Web portal | Browser access | Full (React SPA + auth) | - | - | -Silo has a comprehensive REST API (78 endpoints) and a full web UI with authentication. Odoo ERP integration has config/sync-log scaffolding but push/pull operations are stubs. Remaining gaps: email notifications, task scheduler, dispatch automation. +Silo has a comprehensive REST API (~140 endpoints) and a full web UI with authentication. Odoo ERP integration has config/sync-log scaffolding but push/pull operations are stubs. Job queue with runner management is fully implemented. Remaining gaps: email notifications, dispatch automation. ### C.8 Reporting & Analytics @@ -586,13 +593,13 @@ File storage works well. Thumbnail generation and file preview would significant | Category | Feature | SW PDM Standard | SW PDM Pro | Silo Current | Silo Planned | |----------|---------|-----------------|------------|--------------|--------------| -| **Version Control** | Check-in/out | Yes | Yes | No | Tier 1 | +| **Version Control** | Check-in/out | Yes | Yes | Partial (edit sessions) | Tier 1 | | | Version history | Yes | Yes | Yes | - | | | Rollback | Yes | Yes | Yes | - | | | Revision labels/status | Yes | Yes | Yes | - | | | Revision comparison | Yes | Yes | Yes (metadata) | - | -| **Workflow** | Custom workflows | Limited | Yes | No | Tier 4 | -| | Parallel approval | No | Yes | No | Tier 4 | +| **Workflow** | Custom workflows | Limited | Yes | Yes (YAML state machines) | - | +| | Parallel approval | No | Yes | Yes (multi-stage gates) | - | | | Notifications | No | Yes | No | Tier 1 | | **Security** | User auth | Windows | Windows/LDAP | Yes (local, LDAP, OIDC) | - | | | Permissions | Basic | Granular | Partial (role-based) | Tier 4 | @@ -606,7 +613,7 @@ File storage works well. Thumbnail generation and file preview would significant | **Data** | CSV import/export | Yes | Yes | Yes | - | | | ODS import/export | No | No | Yes | - | | | Project management | Yes | Yes | Yes | - | -| **Integration** | API | Limited | Full | Full REST (78) | - | +| **Integration** | API | Limited | Full | Full REST (~140) | - | | | ERP connectors | No | Yes | Partial (Odoo stubs) | Tier 6 | | | Web access | No | Yes | Yes (React SPA + auth) | - | | **Files** | Versioning | Yes | Yes | Yes | - | diff --git a/docs/src/silo-server/INSTALL.md b/docs/src/silo-server/INSTALL.md index 8dcede5c68..e6e30b74ca 100644 --- a/docs/src/silo-server/INSTALL.md +++ b/docs/src/silo-server/INSTALL.md @@ -491,4 +491,7 @@ After a successful installation: | [SPECIFICATION.md](SPECIFICATION.md) | Full design specification and API reference | | [STATUS.md](STATUS.md) | Implementation status | | [GAP_ANALYSIS.md](GAP_ANALYSIS.md) | Gap analysis and revision control roadmap | +| [MODULES.md](MODULES.md) | Module system specification | +| [WORKERS.md](WORKERS.md) | Job queue and runner system | +| [SOLVER.md](SOLVER.md) | Assembly solver service | | [COMPONENT_AUDIT.md](COMPONENT_AUDIT.md) | Component audit tool design | diff --git a/docs/src/silo-server/MODULES.md b/docs/src/silo-server/MODULES.md index 589e0c4db8..057b84129e 100644 --- a/docs/src/silo-server/MODULES.md +++ b/docs/src/silo-server/MODULES.md @@ -1,7 +1,7 @@ # Module System Specification **Status:** Draft -**Last Updated:** 2026-02-14 +**Last Updated:** 2026-03-01 --- @@ -36,6 +36,8 @@ These cannot be disabled. They define what Silo *is*. | `freecad` | Create Integration | `true` | URI scheme, executable path, client settings | | `jobs` | Job Queue | `false` | Async compute jobs, runner management | | `dag` | Dependency DAG | `false` | Feature DAG sync, validation states, interference detection | +| `solver` | Solver | `false` | Assembly constraint solving via server-side runners | +| `sessions` | Sessions | `true` | Workstation registration, edit sessions, and presence tracking | ### 2.3 Module Dependencies @@ -46,6 +48,8 @@ Some modules require others to function: | `dag` | `jobs` | | `jobs` | `auth` (runner tokens) | | `odoo` | `auth` | +| `solver` | `jobs` | +| `sessions` | `auth` | When enabling a module, its dependencies are validated. The server rejects enabling `dag` without `jobs`. Disabling a module that others depend on shows a warning listing dependents. @@ -257,6 +261,34 @@ PUT /api/items/{partNumber}/dag POST /api/items/{partNumber}/dag/mark-dirty/{nodeKey} ``` +### 3.11 `solver` + +``` +GET /api/solver/jobs +GET /api/solver/jobs/{jobID} +POST /api/solver/jobs +POST /api/solver/jobs/{jobID}/cancel +GET /api/solver/solvers +GET /api/solver/results/{partNumber} +``` + +### 3.12 `sessions` + +``` +# Workstation management +GET /api/workstations +POST /api/workstations +DELETE /api/workstations/{workstationID} + +# Edit sessions (user-scoped) +GET /api/edit-sessions + +# Edit sessions (item-scoped) +GET /api/items/{partNumber}/edit-sessions +POST /api/items/{partNumber}/edit-sessions +DELETE /api/items/{partNumber}/edit-sessions/{sessionID} +``` + --- ## 4. Disabled Module Behavior @@ -431,6 +463,18 @@ GET /api/modules "required": false, "name": "Dependency DAG", "depends_on": ["jobs"] + }, + "solver": { + "enabled": false, + "required": false, + "name": "Solver", + "depends_on": ["jobs"] + }, + "sessions": { + "enabled": true, + "required": false, + "name": "Sessions", + "depends_on": ["auth"] } }, "server": { @@ -518,7 +562,9 @@ Returns full config grouped by module with secrets redacted: "job_timeout_check": 30, "default_priority": 100 }, - "dag": { "enabled": false } + "dag": { "enabled": false }, + "solver": { "enabled": false, "default_solver": "ondsel" }, + "sessions": { "enabled": true } } ``` @@ -632,6 +678,11 @@ modules: default_priority: 100 dag: enabled: false + solver: + enabled: false + default_solver: ondsel + sessions: + enabled: true ``` If a module is not listed under `modules:`, its default enabled state from Section 2.2 applies. The `auth.enabled` field continues to control the `auth` module (no duplication under `modules:`). @@ -732,6 +783,7 @@ These are read-only in the UI (setup-only via YAML/env). The "Test" button is av - **Per-module permissions** — beyond the current role hierarchy, modules may define fine-grained scopes (e.g., `jobs:admin`, `dag:write`). - **Location & Inventory module** — when the Location/Inventory API is implemented (tables already exist), it becomes a new optional module. - **Notifications module** — per ROADMAP.md Tier 1, notifications/subscriptions will be a dedicated module. +- **Soft interference detection** — the `sessions` module currently enforces hard interference (unique index on item + context_level + object_id). Soft interference detection (overlapping dependency cones) is planned as a follow-up. --- diff --git a/docs/src/silo-server/PROJECTS_AND_APPROVALS.md b/docs/src/silo-server/PROJECTS_AND_APPROVALS.md new file mode 100644 index 0000000000..e6a10645dd --- /dev/null +++ b/docs/src/silo-server/PROJECTS_AND_APPROVALS.md @@ -0,0 +1,248 @@ +# Projects & Approvals — Implementation Reference + +## Projects + +### Database Schema + +**Migration 006 + 009** + +```sql +-- projects table +CREATE TABLE projects ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + code VARCHAR(10) UNIQUE NOT NULL, + name TEXT, + description TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by TEXT -- added in migration 009 +); + +-- many-to-many junction +CREATE TABLE item_projects ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + item_id UUID NOT NULL REFERENCES items(id) ON DELETE CASCADE, + project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE(item_id, project_id) +); +``` + +Items can belong to multiple projects. Project codes are immutable after creation. + +### API Endpoints + +All gated by `RequireModule("projects")`. + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/projects` | viewer | List all projects | +| POST | `/api/projects` | editor | Create project (code: 2-10 alphanum) | +| GET | `/api/projects/{code}` | viewer | Get single project | +| PUT | `/api/projects/{code}` | editor | Update name/description | +| DELETE | `/api/projects/{code}` | editor | Delete project (cascades associations) | +| GET | `/api/projects/{code}/items` | viewer | List non-archived items in project | +| GET | `/api/projects/{code}/sheet.ods` | viewer | Export ODS workbook (items + BOMs) | +| GET | `/api/items/{pn}/projects` | viewer | Get projects for an item | +| POST | `/api/items/{pn}/projects` | editor | Add item to projects (`{"projects":["A","B"]}`) | +| DELETE | `/api/items/{pn}/projects/{code}` | editor | Remove item from project | + +### Backend Files + +| File | Contents | +|------|----------| +| `internal/db/projects.go` | `ProjectRepository` — 13 methods: List, GetByCode, GetByID, Create, Update, Delete, AddItemToProject, AddItemToProjectByCode, RemoveItemFromProject, RemoveItemFromProjectByCode, GetProjectsForItem, GetProjectCodesForItem, GetItemsForProject, SetItemProjects | +| `internal/api/handlers.go` | Handlers: HandleListProjects, HandleCreateProject, HandleGetProject, HandleUpdateProject, HandleDeleteProject, HandleGetProjectItems, HandleGetItemProjects, HandleAddItemProjects, HandleRemoveItemProject | +| `internal/api/ods.go` | HandleProjectSheetODS — multi-sheet ODS export with items list + per-assembly BOM sheets | +| `internal/api/routes.go` | Route registration under `/api/projects` and `/api/items/{pn}/projects` | + +### Frontend + +**Page:** `web/src/pages/ProjectsPage.tsx` at route `/projects` + +Features: +- Sortable table (code, name, description, item count, created date) +- Create form with code validation (2-10 chars, auto-uppercase) +- Inline edit (name/description only, code immutable) +- Delete with confirmation dialog +- Item count fetched per-project in parallel +- Catppuccin Mocha theme, conditionally shown in sidebar when module enabled + +### Config + +```yaml +modules: + projects: + enabled: true # default true +``` + +### Not Yet Implemented + +- Project-level permissions / ownership (all projects visible to all viewers) +- Project-based filtering in the main items list page +- Bulk item operations across projects +- Project archival / soft-delete +- Project hierarchies or team assignment + +--- + +## Approvals & ECO Workflows + +### Database Schema + +**Migration 018 + 019** + +```sql +-- item_approvals table +CREATE TABLE item_approvals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + item_id UUID NOT NULL REFERENCES items(id) ON DELETE CASCADE, + workflow_name TEXT, -- added in migration 019 + eco_number TEXT, + state TEXT NOT NULL DEFAULT 'draft', + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_by TEXT +); + +-- approval_signatures table +CREATE TABLE approval_signatures ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + approval_id UUID NOT NULL REFERENCES item_approvals(id) ON DELETE CASCADE, + username TEXT NOT NULL, + role TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + signed_at TIMESTAMPTZ, + comment TEXT +); +``` + +### State Machine + +**Approval states:** `draft` | `pending` | `approved` | `rejected` + +**Signature states:** `pending` | `approved` | `rejected` + +**Auto-transitions** (evaluated after each signature): +1. If any signature is `rejected` and workflow defines `any_reject` rule -> approval becomes `rejected` +2. If all signatures on required gates are `approved` and workflow defines `all_required_approve` rule -> approval becomes `approved` +3. Otherwise -> stays `pending` + +### Workflow Definitions + +YAML files in `workflows/` directory, loaded at server startup. + +**`workflows/engineering-change.yaml`:** +```yaml +workflow: + name: engineering-change + version: 1 + description: "Standard engineering change order with peer review and manager approval" + states: [draft, pending, approved, rejected] + gates: + - role: engineer + label: "Peer Review" + required: true + - role: manager + label: "Manager Approval" + required: true + - role: quality + label: "Quality Sign-off" + required: false + rules: + any_reject: rejected + all_required_approve: approved +``` + +**`workflows/quick-review.yaml`:** +```yaml +workflow: + name: quick-review + version: 1 + description: "Single reviewer approval for minor changes" + states: [draft, pending, approved, rejected] + gates: + - role: reviewer + label: "Review" + required: true + rules: + any_reject: rejected + all_required_approve: approved +``` + +Custom workflows: add a `.yaml` file to the workflows directory with the same structure. + +### API Endpoints + +Not module-gated (always available when server is running). + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/workflows` | viewer | List all loaded workflow definitions | +| GET | `/api/items/{pn}/approvals` | viewer | List approvals for item (with signatures) | +| POST | `/api/items/{pn}/approvals` | editor | Create approval with signers | +| POST | `/api/items/{pn}/approvals/{id}/sign` | editor | Record signature (approve/reject) | + +**Create approval request:** +```json +{ + "workflow": "engineering-change", + "eco_number": "ECO-2026-042", + "signers": [ + { "username": "alice", "role": "engineer" }, + { "username": "bob", "role": "manager" }, + { "username": "carol", "role": "quality" } + ] +} +``` + +Validation: workflow must exist, all signer roles must be defined in the workflow, all required gates must have at least one signer. + +**Sign request:** +```json +{ + "status": "approved", + "comment": "Looks good" +} +``` + +Validation: approval must be in `pending` state, caller must be a listed signer, caller must not have already signed. + +### SSE Events + +Published on the existing `GET /api/events` stream. + +| Event | Payload | Trigger | +|-------|---------|---------| +| `approval.created` | `{part_number, approval_id, workflow, eco_number}` | Approval created | +| `approval.signed` | `{part_number, approval_id, username, status}` | Signature recorded | +| `approval.completed` | `{part_number, approval_id, state}` | All required gates resolved | + +### Backend Files + +| File | Contents | +|------|----------| +| `internal/db/item_approvals.go` | `ItemApprovalRepository` — Create, AddSignature, GetWithSignatures, ListByItemWithSignatures, UpdateState, GetSignatureForUser, UpdateSignature | +| `internal/api/approval_handlers.go` | HandleGetApprovals, HandleCreateApproval, HandleSignApproval, HandleListWorkflows, evaluateApprovalState | +| `internal/workflow/workflow.go` | Workflow/Gate/Rules types, Load, LoadAll, Validate, RequiredGates, HasRole | +| `workflows/engineering-change.yaml` | 3-gate ECO workflow | +| `workflows/quick-review.yaml` | 1-gate quick review workflow | + +### Config + +```yaml +workflows: + directory: /etc/silo/workflows # path to workflow YAML files +``` + +### Frontend + +**No approval UI exists.** The backend API is fully functional but has no web interface. Issue #147 includes an approvals page scaffold as part of the .kc metadata web UI phase. + +### Not Yet Implemented + +- Web UI for creating, viewing, and signing approvals +- Approval history / audit log view +- Lifecycle state transitions tied to approval outcomes (e.g. auto-release on approval) +- Email or notification integration for pending signatures +- Delegation / proxy signing +- Approval templates (pre-filled signer lists per workflow) +- Bulk approval operations diff --git a/docs/src/silo-server/ROADMAP.md b/docs/src/silo-server/ROADMAP.md index b5aedeae9e..56d57e1061 100644 --- a/docs/src/silo-server/ROADMAP.md +++ b/docs/src/silo-server/ROADMAP.md @@ -92,7 +92,7 @@ Everything depends on these. They define what Silo *is*. | **API Endpoint Registry** | Module discovery, dynamic UI rendering, health checks | Not Started | | **Web UI Shell** | App launcher, breadcrumbs, view framework, module rendering | Partial | | **Python Scripting Engine** | Server-side hook execution, module extension point | Not Started | -| **Job Queue Infrastructure** | Redis/NATS shared async service for all compute modules | Not Started | +| **Job Queue Infrastructure** | PostgreSQL-backed async job queue with runner management | Complete | ### Tier 1 -- Core Services @@ -102,7 +102,7 @@ Broad downstream dependencies. These should be built early because retrofitting |--------|-------------|------------|--------| | **Headless Create** | API-driven FreeCAD instance for file manipulation, geometry queries, format conversion, rendering | Core Silo, Job Queue | Not Started | | **Notifications & Subscriptions** | Per-part watch lists, lifecycle event hooks, webhook delivery | Core Silo, Registry | Not Started | -| **Audit Trail / Compliance** | ITAR, ISO 9001, AS9100 traceability; module-level event journaling | Core Silo | Partial | +| **Audit Trail / Compliance** | ITAR, ISO 9001, AS9100 traceability; module-level event journaling | Core Silo | Complete (base) | ### Tier 2 -- File Intelligence & Collaboration @@ -132,7 +132,7 @@ Process modules that formalize how engineering work moves through an organizatio | Module | Description | Depends On | Status | |--------|-------------|------------|--------| -| **Approval / ECO Workflow** | Engineering change orders, multi-stage review gates, digital signatures | Notifications, Audit Trail, Schemas | Not Started | +| **Approval / ECO Workflow** | Engineering change orders, multi-stage review gates, digital signatures | Notifications, Audit Trail, Schemas | Complete | | **Shop Floor Drawing Distribution** | Controlled push-to-production drawings; web-based appliance displays on the floor | Headless Create, Approval Workflow | Not Started | | **Import/Export Bridge** | STEP, IGES, 3MF connectors; SOLIDWORKS migration tooling; ERP adapters | Headless Create | Not Started | | **Multi-tenant / Org Management** | Org boundaries, role-based permissioning, storage quotas | Core Auth, Audit Trail | Not Started | @@ -202,15 +202,15 @@ Implement engineering change processes (Tier 4: Approval/ECO Workflow). | Task | Description | Status | |------|-------------|--------| -| Workflow designer | YAML-defined state machines | Not Started | -| State transitions | Configurable transition rules with permissions | Not Started | -| Approval workflows | Single and parallel approver gates | Not Started | +| Workflow designer | YAML-defined state machines | Complete | +| State transitions | Configurable transition rules with permissions | Complete | +| Approval workflows | Single and parallel approver gates | Complete | | Email notifications | SMTP integration for alerts on state changes | Not Started | **Success metrics:** -- Engineering change process completable in Silo +- ~~Engineering change process completable in Silo~~ Done (YAML-configured workflows with multi-stage gates) - Email notifications delivered reliably -- Workflow state visible in web UI +- ~~Workflow state visible in web UI~~ Available via API ### Search & Discovery @@ -240,9 +240,17 @@ For full SOLIDWORKS PDM comparison tables, see [GAP_ANALYSIS.md Appendix C](GAP_ 5. ~~Multi-level BOM API~~ -- recursive expansion with configurable depth 6. ~~BOM export~~ -- CSV and ODS formats +### Recently Completed + +7. ~~Workflow engine~~ -- YAML-defined state machines with multi-stage approval gates +8. ~~Job queue~~ -- PostgreSQL-backed async compute with runner management +9. ~~Assembly solver service~~ -- server-side constraint solving with result caching +10. ~~Workstation registration~~ -- device identity and heartbeat tracking +11. ~~Edit sessions~~ -- acquire/release with hard interference detection + ### Critical Gaps (Required for Team Use) -1. **Workflow engine** -- state machines with transitions and approvals +1. ~~**Workflow engine**~~ -- Complete (YAML-configured approval workflows) 2. **Check-out locking** -- pessimistic locking for CAD files ### High Priority Gaps (Significant Value) @@ -275,7 +283,7 @@ For full SOLIDWORKS PDM comparison tables, see [GAP_ANALYSIS.md Appendix C](GAP_ 1. **Module manifest format** -- JSON, TOML, or Python-based? Tradeoffs between simplicity and expressiveness. 2. **.kc thumbnail policy** -- Single canonical thumbnail vs. multi-view renders. Impacts file size and generation cost. -3. **Job queue technology** -- Redis Streams vs. NATS. Redis is already in the stack; NATS offers better pub/sub semantics for event-driven modules. +3. ~~**Job queue technology**~~ -- Resolved: PostgreSQL-backed with `SELECT FOR UPDATE SKIP LOCKED` for exactly-once delivery. No external queue dependency. 4. **Headless Create deployment** -- Sidecar container per Silo instance, or pool of workers behind the job queue? 5. **BIM-MES workbench scope** -- How much of FreeCAD BIM is reusable vs. needs to be purpose-built for inventory/facility modeling? 6. **Offline .kc workflow** -- How much of the `silo/` metadata is authoritative when disconnected? Reconciliation strategy on reconnect. @@ -287,7 +295,7 @@ For full SOLIDWORKS PDM comparison tables, see [GAP_ANALYSIS.md Appendix C](GAP_ ### Implemented Features (MVP Complete) #### Core Database System -- PostgreSQL schema with 13 migrations +- PostgreSQL schema with 23 migrations - UUID-based identifiers throughout - Soft delete support via `archived_at` timestamps - Atomic sequence generation for part numbers @@ -340,7 +348,7 @@ For full SOLIDWORKS PDM comparison tables, see [GAP_ANALYSIS.md Appendix C](GAP_ - Template generation for import formatting #### API & Web Interface -- REST API with 78 endpoints +- REST API with ~140 endpoints - Authentication: local (bcrypt), LDAP/FreeIPA, OIDC/Keycloak - Role-based access control (admin > editor > viewer) - API token management (SHA-256 hashed) @@ -371,7 +379,7 @@ For full SOLIDWORKS PDM comparison tables, see [GAP_ANALYSIS.md Appendix C](GAP_ | Part number validation | Not started | API accepts but doesn't validate format | | Location hierarchy CRUD | Schema only | Tables exist, no API endpoints | | Inventory tracking | Schema only | Tables exist, no API endpoints | -| Unit tests | Partial | 11 Go test files across api, db, ods, partnum, schema packages | +| Unit tests | Partial | 31 Go test files across api, db, modules, ods, partnum, schema packages | --- diff --git a/docs/src/silo-server/SOLVER.md b/docs/src/silo-server/SOLVER.md index 5b3f894a40..c4d0a99e46 100644 --- a/docs/src/silo-server/SOLVER.md +++ b/docs/src/silo-server/SOLVER.md @@ -1,8 +1,9 @@ # Solver Service Specification -**Status:** Draft -**Last Updated:** 2026-02-19 +**Status:** Phase 3b Implemented (server endpoints, job definitions, result cache) +**Last Updated:** 2026-03-01 **Depends on:** KCSolve Phase 1 (PR #297), Phase 2 (PR #298) +**Prerequisite infrastructure:** Job queue, runner system, and SSE broadcasting are fully implemented (see [WORKERS.md](WORKERS.md), migration `015_jobs_runners.sql`, `cmd/silorunner/`). --- @@ -38,7 +39,7 @@ This specification describes how the existing KCSolve client-side API (C++ libra │ (SolveContext JSON) │ │ 4. GET /api/events (SSE) - │ solver.progress, solver.completed + │ job.progress, job.completed ▼ ┌─────────────────────┐ │ Silo Server │ @@ -64,8 +65,8 @@ This specification describes how the existing KCSolve client-side API (C++ libra | Component | Role | Deployment | |-----------|------|------------| -| **Silo server** | Job queue management, REST API, SSE broadcast, result storage | Existing `silod` binary | -| **Solver runner** | Claims solver jobs, executes `kcsolve`, reports results | New runner tag `solver` on existing `silorunner` | +| **Silo server** | Job queue management, REST API, SSE broadcast, result storage | Existing `silod` binary (jobs module, migration 015) | +| **Solver runner** | Claims solver jobs, executes `kcsolve`, reports results | Existing `silorunner` binary (`cmd/silorunner/`) with `solver` tag | | **kcsolve module** | Python/C++ solver library (Phase 1+2) | Installed on runner nodes | | **Create client** | Submits jobs, receives results via SSE | Existing FreeCAD client | @@ -394,28 +395,30 @@ Solver jobs emit events on the existing `/api/events` SSE stream. ### 5.1 Event Types +Solver jobs use the existing `job.*` SSE event prefix (see [WORKERS.md](WORKERS.md)). Clients filter on `definition_name` to identify solver-specific events. + | Event | Payload | When | |-------|---------|------| -| `solver.created` | `{job_id, operation, solver, item_part_number}` | Job submitted | -| `solver.claimed` | `{job_id, runner_id, runner_name}` | Runner starts work | -| `solver.progress` | `{job_id, progress, message}` | Progress update (0-100) | -| `solver.completed` | `{job_id, status, dof, diagnostics_count, solve_time_ms}` | Job succeeded | -| `solver.failed` | `{job_id, error_message}` | Job failed | +| `job.created` | `{job_id, definition_name, trigger, item_id}` | Job submitted | +| `job.claimed` | `{job_id, runner_id, runner}` | Runner claims work | +| `job.progress` | `{job_id, progress, message}` | Progress update (0-100) | +| `job.completed` | `{job_id, runner_id}` | Job succeeded | +| `job.failed` | `{job_id, runner_id, error}` | Job failed | ### 5.2 Example Stream ``` -event: solver.created -data: {"job_id":"abc-123","operation":"solve","solver":"ondsel","item_part_number":"ASM-001"} +event: job.created +data: {"job_id":"abc-123","definition_name":"assembly-solve","trigger":"manual","item_id":"uuid-..."} -event: solver.claimed -data: {"job_id":"abc-123","runner_id":"r1","runner_name":"solver-worker-01"} +event: job.claimed +data: {"job_id":"abc-123","runner_id":"r1","runner":"solver-worker-01"} -event: solver.progress +event: job.progress data: {"job_id":"abc-123","progress":50,"message":"Building constraint system..."} -event: solver.completed -data: {"job_id":"abc-123","status":"Success","dof":3,"diagnostics_count":1,"solve_time_ms":127.4} +event: job.completed +data: {"job_id":"abc-123","runner_id":"r1"} ``` ### 5.3 Client Integration @@ -423,8 +426,8 @@ data: {"job_id":"abc-123","status":"Success","dof":3,"diagnostics_count":1,"solv The Create client subscribes to the SSE stream and updates the Assembly workbench UI: 1. **Silo viewport widget** shows job status indicator (pending/running/done/failed) -2. On `solver.completed`, the client can fetch the full result via `GET /api/solver/jobs/{id}` and apply placements -3. On `solver.failed`, the client shows the error in the report panel +2. On `job.completed` (where `definition_name` starts with `assembly-`), the client fetches the full result via `GET /api/jobs/{id}` and applies placements +3. On `job.failed`, the client shows the error in the report panel 4. Diagnostic results (redundant/conflicting constraints) surface in the constraint tree --- @@ -433,7 +436,9 @@ The Create client subscribes to the SSE stream and updates the Assembly workbenc ### 6.1 Runner Requirements -Solver runners are standard `silorunner` instances with the `solver` tag. They require: +Solver runners are standard `silorunner` instances (see `cmd/silorunner/main.go`) registered with the `solver` tag. The existing runner binary already handles the full job lifecycle (claim, start, progress, complete/fail, log, DAG sync). Solver support requires adding `solver-run`, `solver-diagnose`, and `solver-kinematic` to the runner's command dispatch (currently handles `create-validate`, `create-export`, `create-dag-extract`, `create-thumbnail`). + +Additional requirements on the runner host: - Python 3.11+ with `kcsolve` module installed - `libKCSolve.so` and solver backend libraries (e.g. `libOndselSolver.so`) @@ -453,21 +458,28 @@ curl -X POST https://silo.example.com/api/runners \ {"id":"uuid","token":"silo_runner_xyz..."} ``` -### 6.3 Runner Heartbeat +### 6.3 Runner Heartbeat and Capabilities -Runners report solver capabilities during heartbeat: +The existing heartbeat endpoint (`POST /api/runner/heartbeat`) takes no body — it updates `last_heartbeat` on every authenticated request via the `RequireRunnerAuth` middleware. Runners that go 90 seconds without a request are marked offline by the background sweeper. -```json -POST /api/runner/heartbeat -{ - "capabilities": { - "solvers": ["ondsel"], - "api_version": 1, - "python_version": "3.11.11" - } -} +Solver capabilities are reported via the runner's `metadata` JSONB field, set at registration time: + +```bash +curl -X POST https://silo.example.com/api/runners \ + -H "Authorization: Bearer admin_token" \ + -d '{ + "name": "solver-01", + "tags": ["solver"], + "metadata": { + "solvers": ["ondsel"], + "api_version": 1, + "python_version": "3.11.11" + } + }' ``` +> **Future enhancement:** The heartbeat endpoint could be extended to accept an optional body for dynamic capability updates, but currently capabilities are static per registration. + ### 6.4 Runner Execution Flow ```python @@ -529,7 +541,9 @@ The `kcsolve.runner` module reads JSON from stdin, executes the solve, and write ### 7.1 Manual Solve Job -Triggered by the client when the user requests a server-side solve: +Triggered by the client when the user requests a server-side solve. + +> **Note:** The `compute.type` uses `custom` because the valid types in `internal/jobdef/jobdef.go` are: `validate`, `rebuild`, `diff`, `export`, `custom`. Solver commands are dispatched by the runner based on the `command` field. ```yaml job: @@ -544,7 +558,7 @@ job: type: assembly compute: - type: solver + type: custom command: solver-run runner: @@ -574,7 +588,7 @@ job: type: assembly compute: - type: solver + type: custom command: solver-diagnose args: operation: diagnose @@ -604,7 +618,7 @@ job: type: assembly compute: - type: solver + type: custom command: solver-kinematic args: operation: kinematic @@ -671,7 +685,7 @@ with zipfile.ZipFile("assembly.kc") as zf: The solver module uses the existing `jobs` table. One new table is added for result caching: ```sql --- Migration: 020_solver_results.sql +-- Migration: 021_solver_results.sql CREATE TABLE solver_results ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -701,7 +715,7 @@ The `UNIQUE(item_id, revision_number, operation)` constraint means each revision When a solver job completes, the server: 1. Stores the full result in the `jobs.result` JSONB column (standard job result) 2. Upserts a row in `solver_results` for quick lookup by item/revision -3. Broadcasts `solver.completed` SSE event +3. Broadcasts `job.completed` SSE event --- @@ -840,23 +854,22 @@ Add `to_dict()` / `from_dict()` methods to all KCSolve types in the pybind11 mod **Verification:** `ctx.to_dict()` round-trips through `SolveContext.from_dict()`. -### Phase 3b: Server Endpoints +### Phase 3b: Server Endpoints -- COMPLETE -Add the solver module to the Silo server. +Add the solver module to the Silo server. This builds on the existing job queue infrastructure (`migration 015_jobs_runners.sql`, `internal/db/jobs.go`, `internal/api/job_handlers.go`, `internal/api/runner_handlers.go`). -**Files to create (in silo repository):** -- `internal/modules/solver/solver.go` -- Module registration and config -- `internal/modules/solver/handlers.go` -- REST endpoint handlers -- `internal/modules/solver/events.go` -- SSE event definitions -- `migrations/020_solver_results.sql` -- Database migration +**Implemented files:** +- `internal/api/solver_handlers.go` -- REST endpoint handlers (solver-specific convenience layer over existing `/api/jobs`) +- `internal/db/migrations/021_solver_results.sql` -- Database migration for result caching table +- Module registered as `solver` in `internal/modules/modules.go` with `jobs` dependency ### Phase 3c: Runner Support -Add solver job execution to `silorunner`. +Add solver command handlers to the existing `silorunner` binary (`cmd/silorunner/main.go`). The runner already implements the full job lifecycle (claim, start, progress, complete/fail). This phase adds `solver-run`, `solver-diagnose`, and `solver-kinematic` to the `executeJob` switch statement. -**Files to create:** -- `src/Mod/Assembly/Solver/bindings/runner.py` -- `kcsolve.runner` entry point -- Runner capability reporting during heartbeat +**Files to modify:** +- `cmd/silorunner/main.go` -- Add solver command dispatch cases +- `src/Mod/Assembly/Solver/bindings/runner.py` -- `kcsolve.runner` Python entry point (invoked by silorunner via subprocess) ### Phase 3d: .kc Context Packing diff --git a/docs/src/silo-server/STATUS.md b/docs/src/silo-server/STATUS.md index bc66fb7472..9ba7a14425 100644 --- a/docs/src/silo-server/STATUS.md +++ b/docs/src/silo-server/STATUS.md @@ -1,6 +1,6 @@ # Silo Development Status -**Last Updated:** 2026-02-08 +**Last Updated:** 2026-03-01 --- @@ -10,10 +10,10 @@ | Component | Status | Notes | |-----------|--------|-------| -| PostgreSQL schema | Complete | 18 migrations applied | +| PostgreSQL schema | Complete | 23 migrations applied | | YAML schema parser | Complete | Supports enum, serial, constant, string segments | | Part number generator | Complete | Scoped sequences, category-based format | -| API server (`silod`) | Complete | 86 REST endpoints via chi/v5 | +| API server (`silod`) | Complete | ~140 REST endpoints via chi/v5 | | CLI tool (`silo`) | Complete | Item registration and management | | Filesystem file storage | Complete | Upload, download, checksums | | Revision control | Complete | Append-only history, rollback, comparison, status/labels | @@ -35,6 +35,11 @@ | .kc metadata API | Complete | GET/PUT metadata, lifecycle transitions, tag management | | .kc dependency API | Complete | List raw deps, resolve UUIDs to part numbers + file availability | | .kc macro API | Complete | List macros, get source content by filename | +| Approval workflows | Complete | YAML-configurable ECO workflows, multi-stage review gates, digital signatures | +| Solver service | Complete | Server-side assembly constraint solving, result caching, job definitions | +| Workstation registration | Complete | Device identity, heartbeat tracking, per-user workstation management | +| Edit sessions | Complete | Acquire/release locks, hard interference detection, SSE notifications | +| SSE targeted delivery | Complete | Per-item, per-user, per-workstation event filtering | | Odoo ERP integration | Partial | Config and sync-log CRUD functional; push/pull are stubs | | Docker Compose | Complete | Dev and production configurations | | Deployment scripts | Complete | setup-host, deploy, init-db, setup-ipa-nginx | @@ -52,7 +57,7 @@ FreeCAD workbench and LibreOffice Calc extension are maintained in separate repo | Inventory API endpoints | Database tables exist, no REST handlers | | Date segment type | Schema parser placeholder only | | Part number format validation | API accepts but does not validate format on creation | -| Unit tests | 9 Go test files across api, db, ods, partnum, schema packages | +| Unit tests | 31 Go test files across api, db, modules, ods, partnum, schema packages | --- @@ -106,3 +111,8 @@ The schema defines 170 category codes across 10 groups: | 016_dag.sql | Dependency DAG nodes and edges | | 017_locations.sql | Location hierarchy and inventory tracking | | 018_kc_metadata.sql | .kc metadata tables (item_metadata, item_dependencies, item_macros, item_approvals, approval_signatures) | +| 019_approval_workflow_name.sql | Approval workflow name column | +| 020_storage_backend_filesystem_default.sql | Storage backend default to filesystem | +| 021_solver_results.sql | Solver result caching table | +| 022_workstations.sql | Workstation registration table | +| 023_edit_sessions.sql | Edit session tracking table with hard interference unique index | diff --git a/docs/src/silo-server/WORKERS.md b/docs/src/silo-server/WORKERS.md index 28cc0d8360..62dd2adc67 100644 --- a/docs/src/silo-server/WORKERS.md +++ b/docs/src/silo-server/WORKERS.md @@ -1,7 +1,7 @@ # Worker System Specification -**Status:** Draft -**Last Updated:** 2026-02-13 +**Status:** Implemented +**Last Updated:** 2026-03-01 --- diff --git a/docs/src/silo-server/frontend-spec.md b/docs/src/silo-server/frontend-spec.md deleted file mode 100644 index 2214508db7..0000000000 --- a/docs/src/silo-server/frontend-spec.md +++ /dev/null @@ -1,757 +0,0 @@ -# Silo Frontend Specification - -Current as of 2026-02-11. Documents the React + Vite + TypeScript frontend (migration from Go templates is complete). - -## Overview - -The Silo web UI has been migrated from server-rendered Go templates to a React single-page application. The Go templates (~7,000 lines across 7 files) have been removed. The Go API server serves JSON at `/api/*` and the React SPA at `/`. - -**Stack**: React 19, React Router 7, Vite 6, TypeScript 5.7 -**Theme**: Catppuccin Mocha (dark) via CSS custom properties -**Styling**: Inline React styles using `React.CSSProperties` — no CSS modules, no Tailwind, no styled-components -**State**: Local `useState` + custom hooks. No global state library (no Redux, Zustand, etc.) -**Dependencies**: Minimal — only `react`, `react-dom`, `react-router-dom`. No axios, no tanstack-query. - -## Migration Status - -| Phase | Issue | Title | Status | -|-------|-------|-------|--------| -| 1 | #7 | Scaffold React + Vite + TS, shared layout, auth, API client | Code complete | -| 2 | #8 | Migrate Items page with UI improvements | Code complete | -| 3 | #9 | Migrate Projects, Schemas, Settings, Login pages | Code complete | -| 4 | #10 | Remove Go templates, Docker integration, cleanup | Complete | - -## Architecture - -``` -Browser - └── React SPA (served at /) - ├── Vite dev server (development) → proxies /api/* to Go backend - └── Static files in web/dist/ (production) → served by Go binary - -Go Server (silod) - ├── /api/* JSON REST API - ├── /login, /logout Session auth endpoints (form POST) - ├── /auth/oidc OIDC redirect flow - └── /* React SPA (NotFound handler serves index.html for client-side routing) -``` - -### Auth Flow - -1. React app loads, `AuthProvider` calls `GET /api/auth/me` -2. If 401 → render `LoginPage` (React form) -3. Login form POSTs `application/x-www-form-urlencoded` to `/login` (Go handler sets session cookie) -4. On success, `AuthProvider.refresh()` re-fetches `/api/auth/me`, user state populates, app renders -5. OIDC: link to `/auth/oidc` triggers Go-served redirect flow, callback sets session, user returns to app -6. API client auto-redirects to `/login` on any 401 response - -### Public API Endpoint - -`GET /api/auth/config` — returns `{ oidc_enabled: bool, local_enabled: bool }` so the login page can conditionally show the OIDC button without hardcoding. - -## File Structure - -``` -web/ -├── index.html -├── package.json -├── tsconfig.json -├── tsconfig.node.json -├── vite.config.ts -└── src/ - ├── main.tsx Entry point, renders AuthProvider + BrowserRouter + App - ├── App.tsx Route definitions, auth guard - ├── api/ - │ ├── client.ts fetch wrapper: get, post, put, del + ApiError class - │ └── types.ts All TypeScript interfaces (272 lines) - ├── context/ - │ └── AuthContext.tsx AuthProvider with login/logout/refresh methods - ├── hooks/ - │ ├── useAuth.ts Context consumer hook - │ ├── useFormDescriptor.ts Fetches form descriptor from /api/schemas/{name}/form (replaces useCategories) - │ ├── useItems.ts Items fetching with search, filters, pagination, debounce - │ └── useLocalStorage.ts Typed localStorage persistence hook - ├── styles/ - │ ├── theme.css Catppuccin Mocha CSS custom properties - │ └── global.css Base element styles - ├── components/ - │ ├── AppShell.tsx Header nav + user info + - │ ├── ContextMenu.tsx Reusable right-click positioned menu - │ └── items/ Items page components (16 files) - │ ├── ItemsToolbar.tsx Search, filters, layout toggle, action buttons - │ ├── ItemTable.tsx Sortable table, column config, compact rows - │ ├── ItemDetail.tsx 5-tab detail panel (Main, Properties, Revisions, BOM, Where Used) - │ ├── MainTab.tsx Metadata display, project tags editor, file info - │ ├── PropertiesTab.tsx Form/JSON dual-mode property editor - │ ├── RevisionsTab.tsx Revision list, compare diff, status, rollback - │ ├── BOMTab.tsx BOM table with inline CRUD, cost calculations - │ ├── WhereUsedTab.tsx Parent assemblies table - │ ├── SplitPanel.tsx Resizable horizontal/vertical layout container - │ ├── FooterStats.tsx Fixed bottom bar with item counts - │ ├── CreateItemPane.tsx In-pane create form with schema category properties - │ ├── EditItemPane.tsx In-pane edit form - │ ├── DeleteItemPane.tsx In-pane delete confirmation - │ └── ImportItemsPane.tsx CSV upload with dry-run/import flow - └── pages/ - ├── LoginPage.tsx Username/password form + OIDC button - ├── ItemsPage.tsx Orchestrator: toolbar, split panel, table, detail/CRUD panes - ├── ProjectsPage.tsx Project CRUD with sortable table, in-pane forms - ├── SchemasPage.tsx Schema browser with collapsible segments, enum value CRUD - ├── SettingsPage.tsx Account info, API token management - └── AuditPage.tsx Audit completeness (placeholder, expanded in Issue #5) -``` - -**Total**: ~40 source files, ~7,600 lines of TypeScript/TSX. - -## Design System - -### Theme - -Catppuccin Mocha dark theme. All colors referenced via CSS custom properties: - -| Token | Use | -|-------|-----| -| `--ctp-base` | Page background, input backgrounds | -| `--ctp-mantle` | Header background | -| `--ctp-surface0` | Card backgrounds, even table rows | -| `--ctp-surface1` | Borders, dividers, hover states | -| `--ctp-surface2` | Secondary button backgrounds | -| `--ctp-text` | Primary text | -| `--ctp-subtext0/1` | Secondary/muted text, labels | -| `--ctp-overlay0` | Placeholder text | -| `--ctp-mauve` | Brand accent, primary buttons, nav active | -| `--ctp-blue` | Editor role badge, edit headers | -| `--ctp-green` | Success banners, create headers | -| `--ctp-red` | Errors, delete actions, danger buttons | -| `--ctp-peach` | Part numbers, project codes, token prefixes | -| `--ctp-teal` | Viewer role badge | -| `--ctp-sapphire` | Links, collapsible toggles | -| `--ctp-crust` | Dark text on colored backgrounds | - -### Typography - -- Body: system font stack (Inter, -apple-system, etc.) -- Monospace: JetBrains Mono (part numbers, codes, tokens) -- Table cells: 0.85rem -- Labels: 0.85rem, weight 500 -- Table headers: 0.8rem, uppercase, letter-spacing 0.05em - -### Component Patterns - -**Tables**: Inline styles, compact rows (28-32px), alternating `base`/`surface0` backgrounds, sortable headers with arrow indicators, right-click column config (Items page). - -**Forms**: In-pane forms (Infor ERP-style) — not modal overlays. Create/Edit/Delete forms render in the detail pane area with a colored header bar (green=create, blue=edit, red=delete). Cancel returns to previous view. - -**Cards**: `surface0` background, `0.75rem` border radius, `1.5rem` padding. - -**Buttons**: Primary (`mauve` bg, `crust` text), secondary (`surface1` bg), danger (`red` bg or translucent red bg with red text). - -**Errors**: Red text with translucent red background banner, `0.4rem` border radius. - -**Role badges**: Colored pill badges — admin=mauve, editor=blue, viewer=teal. - -## Page Specifications - -### Items Page (completed in #8) - -The most complex page. Master-detail layout with resizable split panel. - -**Toolbar**: Debounced search (300ms) with scope toggle (All/PN/Description), type and project filter dropdowns, layout toggle (horizontal/vertical), export/import/create buttons. - -**Table**: 7 configurable columns (part_number, item_type, description, revision, projects, created, actions). Visibility stored per layout mode in localStorage. Right-click header opens ContextMenu with checkboxes. Compact rows, zebra striping, click to select. - -**Detail panel**: 5 tabs — Main (metadata + project tags + file info), Properties (form/JSON editor, save creates revision), Revisions (compare, status management, rollback), BOM (inline CRUD, cost calculations, CSV export), Where Used (parent assemblies). - -**CRUD panes**: In-pane forms for Create (schema category properties, project tags), Edit (basic fields), Delete (confirmation), Import (CSV upload with dry-run). - -**Footer**: Fixed 28px bottom bar showing Total | Parts | Assemblies | Documents counts, reactive to filters. - -**State**: `PaneMode` discriminated union manages which pane is shown. `useItems` hook handles fetching, search, filters, pagination. `useLocalStorage` persists layout and column preferences. - -### Projects Page (completed in #9) - -Sortable table with columns: Code, Name, Description, Items (count fetched per project), Created, Actions. - -**CRUD**: In-pane forms above the table. Create requires code (2-10 chars, auto-uppercase), name, description. Edit allows name and description changes. Delete shows confirmation with project code. - -**Navigation**: Click project code navigates to Items page with `?project=CODE` filter. - -**Permissions**: Create/Edit/Delete buttons only visible to editor/admin roles. - -### Schemas Page (completed in #9) - -Schema cards with collapsible segment details. Each schema shows name, description, format string, version, and example part numbers. - -**Segments**: Expandable list showing segment name, type badge, description. Enum segments include a values table with code and description columns. - -**Enum CRUD**: Inline table operations — add row at bottom, edit replaces the row, delete highlights the row with confirmation. All operations call `POST/PUT/DELETE /api/schemas/{name}/segments/{segment}/values/{code}`. - -### Settings Page (completed in #9) - -Two cards: - -**Account**: Read-only grid showing username, display name, email, auth source, role (with colored badge). Data from `useAuth()` context. - -**API Tokens**: Create form (name input + button), one-time token display in green banner with copy-to-clipboard, token list table (name, prefix, created, last used, expires, revoke). Revoke has inline confirm step. Uses `GET/POST/DELETE /api/auth/tokens`. - -### Login Page (completed in #9) - -Standalone centered card (no AppShell). Username/password form, OIDC button shown conditionally based on `GET /api/auth/config`. Error messages in red banner. Submit calls `AuthContext.login()` which POSTs form data to `/login` then re-fetches the user. - -### Audit Page (placeholder) - -Basic table showing audit completeness data from `GET /api/audit/completeness`. Will be expanded as part of Issue #5 (Component Audit UI with completeness scoring and inline editing). - -## API Client - -`web/src/api/client.ts` — thin wrapper around `fetch`: - -- Always sends `credentials: 'include'` for session cookies -- Always sets `Content-Type: application/json` -- 401 responses redirect to `/login` -- Non-OK responses parsed as `{ error, message }` and thrown as `ApiError` -- 204 responses return `undefined` -- Exports: `get()`, `post()`, `put()`, `del()` - -## Type Definitions - -`web/src/api/types.ts` — 272 lines covering all API response and request shapes: - -**Core models**: User, Item, Project, Schema, SchemaSegment, Revision, BOMEntry -**Audit**: AuditFieldResult, AuditItemResult, AuditSummary, AuditCompletenessResponse -**Search**: FuzzyResult (extends Item with score) -**BOM**: WhereUsedEntry, AddBOMEntryRequest, UpdateBOMEntryRequest -**Items**: CreateItemRequest, UpdateItemRequest, CreateRevisionRequest -**Projects**: CreateProjectRequest, UpdateProjectRequest -**Schemas**: CreateSchemaValueRequest, UpdateSchemaValueRequest, PropertyDef, PropertySchema -**Auth**: AuthConfig, ApiToken, ApiTokenCreated -**Revisions**: RevisionComparison -**Import**: CSVImportResult, CSVImportError -**Errors**: ErrorResponse - -## Completed Work - -### Issue #10: Remove Go Templates + Docker Integration -- COMPLETE - -Completed in commit `50923cf`. All Go templates deleted, `web.go` handler removed, SPA serves at `/` via `NotFound` handler with `index.html` fallback. `build/package/Dockerfile` added. - -### Remaining Work - -### Issue #5: Component Audit UI (future) - -The Audit page will be expanded with completeness scoring, inline editing, tier filtering, and category breakdowns. This will be built natively in React using the patterns established in the migration. - -## Development - -```bash -# Install dependencies -cd web && npm install - -# Dev server (proxies /api/* to Go backend on :8080) -npm run dev - -# Type check -npx tsc --noEmit - -# Production build -npm run build -``` - -Vite dev server runs on port 5173 with proxy config in `vite.config.ts` forwarding `/api/*`, `/login`, `/logout`, `/auth/*` to the Go backend. - -## Conventions - -- **No modals for CRUD** — use in-pane forms (Infor ERP-style pattern) -- **No shared component library extraction** until a pattern repeats 3+ times -- **Inline styles only** — all styling via `React.CSSProperties` objects, using Catppuccin CSS variables -- **No class components** — functional components with hooks only -- **Permission checks**: derive `isEditor` from `user.role` in each page, conditionally render write actions -- **Error handling**: try/catch with error state, display in red banners inline -- **Data fetching**: `useEffect` + API client on mount, loading/error/data states -- **Persistence**: `useLocalStorage` hook for user preferences (layout mode, column visibility) - -## New Frontend Tasks - -# CreateItemPane — Schema-Driven Dynamic Form - -**Date**: 2026-02-10 -**Scope**: `CreateItemPane.tsx` renders a dynamic form driven entirely by the form descriptor API (`GET /api/schemas/{name}/form`). All field groups, field types, widgets, and category-specific fields are defined in YAML and resolved server-side. -**Parent**: Items page (`ItemsPage.tsx`) — renders in the detail pane area per existing in-pane CRUD pattern. - ---- - -## Layout - -Single-column scrollable form with a green header bar. Field groups are rendered dynamically from the form descriptor. Category-specific field groups appear after global groups when a category is selected. - -``` -┌──────────────────────────────────────────────────────────────────────┐ -│ Header: "New Item" [green bar] Cancel │ Create │ -├──────────────────────────────────────────────────────────────────────┤ -│ │ -│ Category * [Domain buttons: F C R S E M T A P X] │ -│ [Subcategory search + filtered list] │ -│ │ -│ ── Identity ────────────────────────────────────────────────────── │ -│ [Type * (auto-derived from category)] [Description ] │ -│ │ -│ ── Sourcing ────────────────────────────────────────────────────── │ -│ [Sourcing Type v] [Manufacturer] [MPN] [Supplier] [SPN] │ -│ [Sourcing Link] │ -│ │ -│ ── Cost & Lead Time ────────────────────────────────────────────── │ -│ [Standard Cost $] [Lead Time Days] [Min Order Qty] │ -│ │ -│ ── Status ──────────────────────────────────────────────────────── │ -│ [Lifecycle Status v] [RoHS Compliant ☐] [Country of Origin] │ -│ │ -│ ── Details ─────────────────────────────────────────────────────── │ -│ [Long Description ] │ -│ [Projects: [tag][tag] type to search... ] │ -│ [Notes ] │ -│ │ -│ ── Fastener Specifications (category-specific) ─────────────────── │ -│ [Material] [Finish] [Thread Size] [Head Type] [Drive Type] ... │ -│ │ -└──────────────────────────────────────────────────────────────────────┘ -``` - -## Data Source — Form Descriptor API - -All form structure is fetched from `GET /api/schemas/kindred-rd/form`, which returns: - -- `category_picker`: Multi-stage picker config (domain → subcategory) -- `item_fields`: Definitions for item-level fields (description, item_type, sourcing_type, etc.) -- `field_groups`: Ordered groups with resolved field metadata (Identity, Sourcing, Cost, Status, Details) -- `category_field_groups`: Per-category-prefix groups (e.g., Fastener Specifications for `F` prefix) -- `field_overrides`: Widget hints (currency, url, select, checkbox) - -The YAML schema (`schemas/kindred-rd.yaml`) is the single source of truth. Adding a new field or category in YAML propagates to all clients with no code changes. - -## File Location - -`web/src/components/items/CreateItemPane.tsx` - -Supporting files: - -| File | Purpose | -|------|---------| -| `web/src/components/items/CategoryPicker.tsx` | Multi-stage domain/subcategory selector | -| `web/src/components/items/FileDropZone.tsx` | Drag-and-drop file upload | -| `web/src/components/items/TagInput.tsx` | Multi-select tag input for projects | -| `web/src/hooks/useFormDescriptor.ts` | Fetches and caches form descriptor from `/api/schemas/{name}/form` | -| `web/src/hooks/useFileUpload.ts` | Manages presigned URL upload flow | - -## Component Breakdown - -### CreateItemPane - -Top-level orchestrator. Renders dynamic form from the form descriptor. - -**Props** (unchanged interface): - -```typescript -interface CreateItemPaneProps { - onCreated: (item: Item) => void; - onCancel: () => void; -} -``` - -**State**: - -```typescript -const { descriptor, categories, loading } = useFormDescriptor(); -const [category, setCategory] = useState(''); // selected category code, e.g. "F01" -const [fields, setFields] = useState>({}); // all field values keyed by name -const [error, setError] = useState(null); -const [submitting, setSubmitting] = useState(false); -``` - -A single `fields` record holds all form values (both item-level and property fields). The `ITEM_LEVEL_FIELDS` set (`description`, `item_type`, `sourcing_type`, `long_description`) determines which fields go into the top-level request vs. the `properties` map on submission. - -**Auto-derivation**: When a category is selected, `item_type` is automatically set based on the `derived_from_category` mapping in the form descriptor (e.g., category prefix `A` → `assembly`, `T` → `tooling`, default → `part`). - -**Dynamic rendering**: A `renderField()` function maps each field's `widget` type to the appropriate input: - -| Widget | Rendered As | -|--------|-------------| -| `text` | `` | -| `number` | `` | -| `textarea` | `