Commit graph

213 commits

Author SHA1 Message Date
Grovy311
80dbc73230 Merge fix/client-id-insecure-context for client-side ids 2026-09-26 14:22:35 +02:00
Grovy311
5f4d9bf789 Show the sidebar on every page except the print view
AppShell showed the sidebar only on the project list and overview, so it
disappeared on the building structure page and in the circuit list
editor. It is collapsible, so it now stays on every page. Only the print
view, a stand-alone document opened in its own tab and printed as PDF,
keeps the full width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 13:00:20 +02:00
Grovy311
cd89c4a499 Add buildings above floors and a building structure page
Projects with several buildings could not be structured: floors and rooms
hung directly off the project. Floors can now belong to a building
(migration 0009: table buildings, nullable floors.building_id). Rooms and
distribution boards keep their floor; their building follows from it.
Existing floors stay without a building.

Buildings use the location-structure command store with
project-building.insert/.update/.delete, inverses and persistent Undo/Redo;
deleting a building with assigned floors is rejected. Floor commands carry
buildingId since command version 2. Version 1 floor commands from existing
history predate buildings and are upgraded with buildingId = null before
they are executed and persisted, so forward and inverse keep one version.

Project snapshot schema 7 stores buildings and floor.buildingId; versions
1-6 are upgraded without buildings. Restore and portable transfer include
buildings, and the transfer remaps building ids.

Buildings, floors and rooms move from the project overview to their own
page /projects/:id/struktur with three columns (Gebäude -> Etagen -> Räume)
that filter each other, and a sidebar entry. Floor selects in the board and
room dialogs are grouped by building. Unchanged dialogs close without a
command. Details in docs/building-structure.md; see issue #6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 12:52:34 +02:00
Grovy311
79fcb75ffb Create client ids without crypto.randomUUID
crypto.randomUUID() is only defined in secure contexts (HTTPS or
localhost). Opened over plain HTTP from another machine in the LAN, the
circuit list editor failed with "crypto.randomUUID is not a function"
whenever it created a circuit, device row, circuit group or distribution
board component, including typing into the -frei- row.

createClientId() uses crypto.randomUUID() where available and otherwise
builds an RFC 4122 version 4 UUID from crypto.getRandomValues(), which
works in every context. AGENTS.md notes the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 12:01:43 +02:00
Grovy311
7692699ee2 Add print cover fields and office profile
- Project settings gain siteAddress, workStage, projectLead and
  projectEditor (project.update-settings schema version 3, migration 0009);
  versions 2 and 1 stay executable and keep the current cover values.
  Snapshot schema 6 carries the fields; older versions upgrade with null.
- Global office profile (office name/address) next to the document logo:
  table document_branding_settings, PUT /api/document-branding/office,
  editable from the print view toolbar.
- Cover page reads all fields; empty values show the "[ergänzen]" marker.
  The Planstände editor column shows the project editor.
- Shared logo constants moved to a dependency-free module so the Next.js
  bundler can import them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:39:18 +02:00
Grovy311
4779e4a1a7 Add print view, board info card and document logo upload
- Seitenansicht (/projects/:projectId/print): tabular cover page, board
  overview with supply structure and power sums, one plan per board with a
  repeating title block; PDF via the browser print dialog with named @page
  rules (A4 portrait cover, A4/A3 landscape plans, page x of y).
- Distribution-board info card (board number, location, fed-from, remark)
  in the board edit dialog, persisted through the additively extended
  distribution-board.update command (migration 0008, snapshot schema 6,
  legacy subtree commands normalized).
- Global document logo upload (PNG/JPEG/SVG, max 2 MB) outside project
  revisions and snapshots.
- fed-from stays free text; the overview hierarchy and cumulated sums are
  preliminary until the feed link is modeled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:38:59 +02:00
2e3e85a8e5 Harden the container runtime and shut down cleanly
Running the stack on built output stopped the idle CPU load, but the
remaining findings from the deployment report were still open.

The Dockerfile is now multi-stage. The prod target runs on node:24-slim as
the "node" user instead of root and carries only its runtime dependencies;
"npm prune --omit=dev" drops tsx, drizzle-kit and typescript. The migration
scripts need drizzle-orm and better-sqlite3, both regular dependencies, so
they keep working. The dev target keeps devDependencies for compose.dev.yaml.

Both compose files get a mem_limit. The server stack also sets memswap_limit
to the same value, so a leak lets the OOM killer replace the container
instead of pushing the host into swap. The Express API is no longer
published on the host: it has no authentication and is reached through the
Next.js rewrite. Development ports are bound to the loopback interface.
Services drop all capabilities and run with no-new-privileges.

SIGTERM and SIGINT now close the HTTP server and the SQLite handle and exit,
instead of only logging the signal and waiting for the container's SIGKILL
ten seconds later. server.closeIdleConnections() releases the keep-alive
sockets the Next.js server holds, which would otherwise keep server.close()
pending for the whole grace period. The compose start commands exec into
node, because otherwise sh stays the foreground process and the signal never
reaches it.

Delete scripts/docker-start.sh rather than fixing its unsupervised
background API: both compose stacks set their own command, so the
single-container entrypoint has no caller left. Also remove drizzle.config.js
(a stray tsc artifact drizzle-kit never reads) and the pre-Next index.html
and styles.css.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 11:04:34 +02:00
204499d7e4 Add Revit Dynamo diagnostic scripts 2026-08-23 23:13:35 +02:00
906aa751c7 Run the Docker stack on built output instead of dev servers
compose.yaml overrode the image's own entrypoint to start "tsx watch" and
"next dev" with CHOKIDAR_USEPOLLING and WATCHPACK_POLLING enabled. The
built dist/ and .next from the image were never used, and the polling
watchers kept both containers busy around the clock whether or not
anyone used the application.

Split the two use cases: compose.yaml now runs node dist/server/index.js
and next start with no source mounts and no watchers, and the watching
setup moves to compose.dev.yaml.

next build writes the rewrite destinations from next.config.mjs into
.next/routes-manifest.json, so next start cannot pick up API_INTERNAL_URL
at runtime the way next dev does. Pass it as a build argument.

Give the web container its own probe. The healthcheck hit "/", which
redirects to "/projects", and fetch follows redirects, so every probe
rendered two pages every five seconds. "/health" could not serve as the
probe because next.config.mjs rewrites it to the API. Add "/web-health"
as a route handler, exclude it from the proxy matcher, and relax both
intervals to 30s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:25:06 +02:00
c6cdfc42d5 Keep manual quantity consistent when synchronizing device rows
Synchronizing a project device pushes its quantity onto every linked
device row, but manualQuantity is not part of the sync snapshot and was
left untouched. A device whose quantity is lower than the row's left the
row at manualQuantity > quantity, violating the snapshot invariant.

The violation surfaced far from its cause: the invariant is only checked
when a full state snapshot is read, and the automatic snapshot runs every
25 revisions. A project could therefore accumulate the broken row
silently and then reject every subsequent command, because the failing
validation rolls back the whole transaction including the revision bump.

Recompute manualQuantity as the synchronized quantity minus the total of
the linked external objects, matching the invariant that
assertCircuitDeviceRowQuantity enforces elsewhere, and reject a
synchronized quantity that falls below that total.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:05:50 +02:00
9504905c8f Upgrade project to Node 24
Node 22 leaves active LTS at the end of October. Move the Docker base
image and the documented local requirement to Node 24.

Pull @types/node from ^25 down to ^24 so the type definitions no longer
describe a newer runtime than the one in use. Types ahead of the runtime
let TypeScript accept APIs that are not there at execution time; the
opposite direction is harmless.

Add an engines field to record the supported version. No .nvmrc, since
the project is not used with a Node version manager.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:05:50 +02:00
a17e2e3f4b Merge remote-tracking branch 'origin/feature/amev-picker-and-collapsible-sidebar' 2026-08-06 22:01:54 +02:00
64ccd1f829 Add dark mode toggle in the sidebar
Adds a persistent dark/light theme toggle (localStorage +
data-bs-theme on <html>) so it applies globally, including the
circuit editor route which has no visible sidebar. An inline
beforeInteractive script sets the theme before hydration to avoid a
flash of the wrong theme.

Bootstrap components pick up data-bs-theme automatically; the custom
circuit-grid CSS previously used ~150 hardcoded hex colors, all
converted to semantic tokens in globals.css with light/dark values.

Verified with a headless-browser pass across the project list, project
detail page, circuit editor, project-device drawer and a settings
modal in both themes; no console errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 21:56:21 +02:00
a99980c47b Update docs for the code-review fixes
- circuit-list-editor-api.md: next-identifier moved under
  /projects/:projectId and now documents its 404 scoping behavior.
- current-architecture.md: clarify that the BMK trigger register's
  DB-level normalization is ASCII-only and describe the added
  application-level Unicode-aware uniqueness check that closes the
  umlaut gap; note the additive migration 0006 indexes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 21:33:28 +02:00
b45dc5002d Fix code-review findings across domain, persistence, server and frontend
Full-codebase review turned up five real correctness/security bugs and
a dozen smaller inconsistencies; all are fixed here with matching test
coverage:

- BMK uniqueness silently allowed German-umlaut duplicates ("Ä1" vs
  "ä1") because the DB's normalized index only folds ASCII case. Added
  a shared Unicode-aware pre-check used by every circuit/component
  insert and rename path (one of which had no pre-check at all).
- CircuitDeviceRow.simultaneityFactor had no upper bound at the row
  level (command model and snapshot/restore schema), unlike every
  sibling entity, letting a bad value silently corrupt power totals.
- Grid cell editing silently misread German thousands-separator input
  ("1.500" parsed as 1.5); "." is now rejected outright with a clear
  message instead of guessing.
- The editor's shared command runner (runCommand/applyHistory) had no
  re-entrancy guard, so a double click/drop could fire the same
  command twice and race a BMK collision or revision conflict. Added a
  synchronous ref guard plus isSaving on the buttons that lacked it.
- GET .../next-identifier leaked circuit-numbering state for sections
  in other projects (no ownership check, 400 instead of 404). Moved
  under /projects/:projectId and scoped it.

Also: added the missing circuits.section_id / circuit_device_rows.
circuit_id indexes (migration 0006), gave FormModal a focus trap /
Escape-to-close / focus restore and rebuilt ProjectSettingsModal on
top of it instead of duplicated markup, removed dead code (3 orphaned
domain model files, an unused persistence helper, a wrapper only used
by its own test), pointed the project page at GET /projects/:id
instead of listing+filtering client-side, closed the gap between the
documented 18 MB CSV limit and the ~17.17 MiB actually enforced, added
missing upper bounds on several free-text fields, filled in nine
missing German labels in the revision timeline, replaced a
key-order-fragile JSON.stringify equality check with a real field
comparison, made an implicit sort-order assumption in three
renumbering helpers explicit, cleared the sidebar's target selection
when it no longer resolves after a tree reload, and fixed
updateGlobalDevice to check-then-write instead of write-then-check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 21:31:16 +02:00
fa96be2d42 Fix crash handling and log noise in logging feature
Review of the logging work from the previous commit turned up a real
correctness issue: the uncaughtException/unhandledRejection handlers
logged the error but let the process keep running, which silently
disabled Node's default crash-on-fatal-error behavior and could leave
a zombie process serving broken requests instead of restarting. Both
processes now log and then exit(1); restart: unless-stopped is added
to both services so Docker actually brings them back up.

Also: harden the logger against JSON.stringify throwing on
non-serializable meta, log aborted (closed-before-finished) requests
in the API access log, and stop the Next.js proxy from logging the
Docker healthcheck's request to "/" every few seconds so real
navigation events aren't drowned out.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 19:58:31 +02:00
ea3c02cd6b Add configurable structured logging
Adds a leveled JSON logger (error/warn/info/verbose/debug, controlled via
LOG_LEVEL) wired into the Express API (access log, error middleware,
crash handlers, memory heartbeat) and the Next.js server (page-request
proxy, instrumentation crash handlers, heartbeat). LOG_LEVEL is exposed
through compose.yaml, and both services now rotate their Docker logs
(json-file, 20m x 10 files) instead of growing unbounded. Intended to
capture long-running diagnostic data for the intermittent 502s seen on
the Docker host.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 19:45:39 +02:00
root
f26c000007 Add local uris to nextjs config 2026-08-06 18:58:56 +02:00
Grovy311
9bd22dfcb7 Add AMEV Gleichzeitigkeitsfaktor-Auswähler und einklappbare Seitenleiste
- Gzf-Picker-Modal mit AMEV-Tabelle 3 (Planungshilfe für elektrische
  Leistungsbilanzen) im Projektgerät-Modal und im globalen Geräteformular
- Docker-Deploy-Tooling (docker-start.sh, run-migrations.js) für den
  produktiven Container
- Seitenleiste ist jetzt einklappbar (Icon-only, Zustand in localStorage)
2026-08-03 17:07:14 +02:00
74bea1a3c9 Add Revit circuit list object feed 2026-08-02 18:59:01 +02:00
a69b7b603f Add external object new circuit command 2026-08-02 18:55:57 +02:00
347b3717c3 Add external object new row command 2026-08-02 18:44:46 +02:00
1a7dd7fe03 Add external object row assignment command 2026-08-02 18:38:38 +02:00
dac19b093c Add external row quantity foundation 2026-08-02 18:26:22 +02:00
bc2bc7ece5 Add Revit catalog creation flow 2026-08-02 18:08:18 +02:00
ab2449419b Add Revit initial import wizard 2026-08-02 17:52:21 +02:00
8646f346e6 Add Revit initial import apply API 2026-08-02 17:42:29 +02:00
c9e5fdd876 Add Revit initial import planning 2026-08-02 17:38:17 +02:00
c634570018 Add atomic Revit initial import 2026-08-02 17:33:32 +02:00
e87a3289fa Include external models in project snapshots 2026-08-02 17:25:25 +02:00
76493b573e Add external model persistence foundation 2026-08-02 17:18:24 +02:00
d0fc1a7caf Widen Revit CSV dialog 2026-08-02 17:10:22 +02:00
a9d6301753 Add Revit CSV project dialog 2026-08-02 16:59:18 +02:00
caa6caf99f Add Revit CSV preview API 2026-08-02 16:49:42 +02:00
219da5ec3b Persist Revit CSV configuration 2026-08-02 16:33:18 +02:00
da689af518 Verify Revit reference CSV 2026-08-02 16:01:05 +02:00
bfe8b790b2 Add Revit CSV transport 2026-08-02 15:56:02 +02:00
43cee890e7 Define Revit CSV contracts 2026-08-02 15:51:52 +02:00
927b868dcb Plan Revit CSV integration 2026-08-02 15:47:12 +02:00
c96ba1cbaf Added revit specs 2026-08-02 15:38:02 +02:00
44b6fde940 Add public building project setting 2026-07-31 17:41:30 +02:00
43a3bb0b69 Align documentation with clean baseline 2026-07-31 16:41:37 +02:00
facf788ce8 Remove circuit structure preview 2026-07-31 14:52:37 +02:00
1eed19ef6b Add editable project floors and rooms 2026-07-31 14:46:06 +02:00
1e3c721cfa Derive project device phases from category 2026-07-31 14:37:45 +02:00
5fb81bab04 Support decimal commas and cell units 2026-07-31 14:33:47 +02:00
734a2bcfc4 Create clean release database baseline 2026-07-31 14:07:26 +02:00
5bee3cb103 Add distribution board copy and delete controls 2026-07-31 12:03:52 +02:00
86f1d42e60 Expose distribution board copy and delete API 2026-07-31 11:59:31 +02:00
0fd564a930 Persist complete distribution board subtrees 2026-07-31 11:57:10 +02:00