- Cross-section rows now include maxLengthForVoltageDropM, the inverse of
the voltage-drop formula: the longest single run that stays within the
requested max voltage drop at the given load/cosPhi/phase. Shown as a KPI
and in the ok-summary alert.
- Added PRACTICAL_MINIMUM_CROSS_SECTION_MM2 (2.5 mm² for single_phase
circuits) - a planning convention already named in this projects own
docs/spec/06-future-sizing-and-calculations.md, not a thermal/
voltage-drop requirement. Only ever raises a calculated recommendation,
never lowers one that already needs more. circuitCategory is looked up
from the circuits section and passed through from the editor.
- The manual cross-section field now warns (not blocks - manual override
must stay possible per the same spec doc) when the typed value is not a
standard cross-section, is smaller than the last calculation, or is
below the practical minimum for single_phase circuits.
- Modal title now includes the circuit displayName next to the equipment
identifier.
- Replaced the calculator emoji trigger icon with an inline SVG - the emoji
did not render in at least one tested environment (missing font glyph),
SVG has no such dependency.
- calculateCableSizing now selects cross-section against
designCurrentA = max(operatingCurrentA, existingProtectionRatedCurrentA)
instead of the load current alone. A breaker only trips at its own rated
current (In), so a cable sized for the actual load could overheat under
sustained current below that threshold - the standard In <= Iz rule.
Voltage-drop calculation still uses the real operating current, only the
capacity check changed. Alerts and the ok-summary now name the breaker as
the limiting factor when it is one.
- Added a manual cable type/cross-section entry in the modal itself:
running a calculation pre-fills these fields as a suggestion, but they
are the single field that actually gets applied, and stay editable -
this restores the direct manual-entry capability the modal replaced when
it took over the cableSummary/cableCrossSection cell click.
- Fixed calculator icon position from a trailing ::after (inconsistent
position depending on cell text length, easy to miss on empty cells) to
a fixed-position ::before, so it is always in the same place regardless
of whether cable data is already filled in.
- No new display for the existing protection device - it already has its
own Schutz column in this app.
Trigger now matches cableSummary OR cableCrossSection so it still
shows up regardless of which cable-related column a user has visible
(column visibility is a per-browser preference in this app, not fixed
- see docs/cable-sizing-module.md coupling note). Icon changed to a
calculator glyph.
The trigger was wired to the cableCrossSection column, which is not in
the default visible column set - the actually visible "Kabel" column
is cableSummary (a computed cableType/cableCrossSection/cableLength
join). Editing cableSummary already maps to the remark field rather
than the underlying cable data (see buildCircuitEditPatch), so this
column was effectively a read-only display already; retargeting the
trigger there loses no existing manual-edit capability. Also adds
.cell-cable-sizing-trigger styling (mirrors .cell-protection-trigger)
so the trigger is visually discoverable, which the initial version was
missing entirely.
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>
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>
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>
- 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)
Bring the Econsult-family design tokens (see design-system-econsult.md)
into leistungsbilanz-ts and add a persistent sidebar for the projects
area, mirroring elt-planung-suite / Einsatz-Orga. The sidebar lists all
projects and, inside a project, jumps to its sections; the tree editor
stays sidebar-free since it needs the full width. The projects overview
page is decluttered to a single stacked column (create/import/global
devices) now that the project list lives in the sidebar.