diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c872416
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+*.log
+.DS_Store
+dist/*.map
diff --git a/ANALYSIS.md b/ANALYSIS.md
new file mode 100644
index 0000000..0c12888
--- /dev/null
+++ b/ANALYSIS.md
@@ -0,0 +1,422 @@
+# draw.io Custom Shapes with JavaScript Logic — Architecture Analysis
+
+## Executive Summary
+
+**Can you build custom shapes with JS logic and import them as a library into draw.io?**
+
+**YES** — via the **Plugin system**. draw.io supports fully programmable shapes written in JavaScript that can be loaded as plugins in both the desktop (Electron) app and self-hosted/web instances. These plugins can define shapes with conditional rendering, computed geometry, dynamic behavior, and expose them in the sidebar palette for drag-and-drop use.
+
+The standard "library import" (File > Open Library) only supports declarative XML — but plugins give you **more** power, not less.
+
+---
+
+## Table of Contents
+
+1. [Shape Systems Overview](#shape-systems-overview)
+2. [XML Stencils (Declarative)](#xml-stencils-declarative)
+3. [JavaScript Shape Classes (Programmatic)](#javascript-shape-classes-programmatic)
+4. [The Plugin Architecture](#the-plugin-architecture)
+5. [Loading Plugins in Desktop App](#loading-plugins-in-desktop-app)
+6. [Loading Plugins in Web/Self-Hosted](#loading-plugins-in-webself-hosted)
+7. [Shape Registration Flow](#shape-registration-flow)
+8. [Canvas API Reference](#canvas-api-reference)
+9. [Limitations and Considerations](#limitations-and-considerations)
+10. [Comparison Matrix](#comparison-matrix)
+
+---
+
+## Shape Systems Overview
+
+draw.io has **two completely separate shape definition systems**:
+
+| System | Format | JS Logic? | Import Method |
+|--------|--------|-----------|---------------|
+| XML Stencils | `.xml` stencil files | ❌ No (declarative only) | `mxStencilRegistry` / Open Library |
+| JS Shape Classes | `.js` files | ✅ Full programmatic | Plugin system / `mxCellRenderer.registerShape()` |
+
+When a cell is rendered, the resolution order is:
+
+```
+Cell style "shape=NAME" →
+ 1. mxCellRenderer.defaultShapes[NAME] → JS Shape class
+ 2. mxStencilRegistry.stencils[NAME] → XML Stencil instance
+ 3. Dynamic loading (if enabled) → Load from library registry
+```
+
+---
+
+## XML Stencils (Declarative)
+
+Located in: `src/main/webapp/stencils/*.xml`
+
+XML stencils define shapes using a drawing vocabulary:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Capabilities
+- Drawing primitives: `move`, `line`, `quad`, `curve`, `arc`, `rect`, `roundrect`, `ellipse`, `close`
+- Styling: `strokecolor`, `fillcolor`, `fontcolor`, `alpha`, `strokewidth`, `dashed`
+- State: `save` / `restore`
+- Sub-shapes: ``
+
+### Limitations
+- **No loops, conditionals, or computed geometry**
+- **No JavaScript execution** (except limited `allowEval` for text/image values)
+- Purely static vector drawing instructions
+
+---
+
+## JavaScript Shape Classes (Programmatic)
+
+Located in: `src/main/webapp/shapes/*.js` (~47 files)
+
+Examples: `mxBootstrap.js`, `mxKubernetes.js`, `mxElectrical.js`, `mxNetworks.js`, `mxRack.js`
+
+JS shapes extend `mxShape` and have **full programmatic power**:
+
+```javascript
+function MyShape(bounds, fill, stroke, strokewidth) {
+ mxShape.call(this);
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+}
+
+mxUtils.extend(MyShape, mxShape);
+
+MyShape.prototype.paintVertexShape = function(c, x, y, w, h) {
+ c.translate(x, y);
+
+ // Full JS: conditionals, loops, math, style reading
+ var mode = mxUtils.getValue(this.style, 'mode', 'default');
+ var segments = parseInt(mxUtils.getValue(this.style, 'segments', '4'));
+
+ if (mode === 'alert') {
+ c.setFillColor('#ff4444');
+ }
+
+ // Computed geometry
+ for (var i = 0; i < segments; i++) {
+ var segH = h / segments;
+ c.rect(0, i * segH, w, segH - 2);
+ c.fillAndStroke();
+ }
+};
+
+// Register globally
+mxCellRenderer.registerShape('mxgraph.myLib.myShape', MyShape);
+```
+
+### Capabilities
+- Conditional rendering based on style properties
+- Loops and computed geometry
+- Math and dynamic calculations
+- Reading per-instance style parameters via `mxUtils.getValue(this.style, ...)`
+- Full canvas API access
+- Custom connection points
+- Override `getConstraints()` for dynamic connection point generation
+
+---
+
+## The Plugin Architecture
+
+### Plugin Structure
+
+Every draw.io plugin follows this pattern:
+
+```javascript
+// Shape definitions (execute immediately at load time)
+function CustomShape() { /* ... */ }
+mxUtils.extend(CustomShape, mxShape);
+CustomShape.prototype.paintVertexShape = function(c, x, y, w, h) { /* ... */ };
+mxCellRenderer.registerShape('mxgraph.custom.shapeName', CustomShape);
+
+// UI integration (executes when draw.io is ready)
+Draw.loadPlugin(function(ui) {
+ // ui = EditorUi instance — full access to the application
+
+ // Add sidebar palette
+ var fns = [
+ ui.sidebar.createVertexTemplateEntry(
+ 'shape=mxgraph.custom.shapeName;fillColor=#dae8fc;',
+ 120, 80, '', 'My Custom Shape', null, null, 'search keywords'
+ )
+ ];
+ ui.sidebar.addPaletteFunctions('customPalette', 'My Custom Library', true, fns);
+
+ // Can also: add menus, actions, event listeners, modify graph behavior
+});
+```
+
+### What `Draw.loadPlugin(callback)` Provides
+
+The callback receives `ui` (EditorUi instance) with access to:
+
+| Object | Purpose |
+|--------|---------|
+| `ui.editor.graph` | The mxGraph instance (model, view, styles) |
+| `ui.sidebar` | Sidebar/palette management |
+| `ui.menus` | Menu system |
+| `ui.actions` | Action registry |
+| `ui.editor` | Editor instance |
+| Global: `mxShape`, `mxCellRenderer`, `mxUtils`, `mxConstants` | All mxGraph APIs |
+
+### Production Example: `rackF5.js`
+
+The `plugins/rackF5.js` file (1674 lines) is a real-world plugin that:
+- Defines ~20+ shape classes with full JS rendering
+- Uses conditionals based on style (`hasEars`, `isFront`, `isDC`, `psNum`)
+- Registers all shapes with `mxCellRenderer.registerShape()`
+- Adds a complete sidebar palette via `Draw.loadPlugin()`
+
+---
+
+## Loading Plugins in Desktop App
+
+The draw.io Electron app (desktop) has **full plugin support** via **Extras > Plugins**:
+
+### Method 1: Built-in Plugins
+A dropdown of pre-packaged plugins (`App.publicPlugin`):
+- `ex` (explore), `tips`, `svgdata`, `number`, `sql`, `props`, `text`, `anim`, `update`, `trees`, `replay`, `anon`, `webcola`, `tags`
+
+### Method 2: External Plugins (Custom `.js` Files)
+
+1. **Enable external plugins** (one-time):
+ - The app checks `requestSync('isPluginsEnabled')`
+ - If disabled, it shows "pluginsDisabled" message
+ - Enable via app preferences or configuration
+
+2. **Install your plugin**:
+ - Extras > Plugins → "Select File..."
+ - Native file dialog opens, filtered for `.js` files
+ - Plugin is copied to App Data folder (`installPlugin` action)
+ - Registered in `mxSettings`
+
+3. **Restart draw.io** — plugin loads on next startup
+
+### Loading sequence (from source):
+```
+App startup
+ → mxSettings.getPlugins() returns saved plugin list
+ → App.initPluginCallback() creates Draw object + queue
+ → For each plugin:
+ ├─ Built-in (starts with ./plugins/) → load relative path
+ └─ External → requestSync('getPluginFile') → load from file:// App Data
+ → Scripts loaded via