Renumber the users migration to 0013_users so it follows the upstream
0012_good_gateway (logical board feeds); AGENTS.md deferred-work list keeps
both the new feed items and the auth items.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Security: per-username login throttling, constant-time unknown-user check,
Sec-Fetch-Site CSRF guard for writes, session rotation on login and
password change, absolute session lifetime, sessions ended on reset and
deactivation, last-admin protection, input length caps, ASCII usernames,
clickjacking headers. Covered by tests/authentication.test.ts.
Filter panel: toggle, fields and count share one row to save space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Square icon-only theme toggle sits next to the Abmelden button in a shared
row, collapsing to just the toggle when the sidebar is collapsed - same
arrangement as pm-app's Sidebar.tsx.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No self-signup: admins create every account in Config -> Benutzer with a
typed start password; a forced password change gates first use. Session-
cookie auth protects all /api/* routes; /api/users additionally requires
the admin role. See docs/authentication.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Document filter combinations, reset and browser persistence. Keep the search hint applicable before board information fields are introduced.
Validation: 20 focused tests, 431 total tests, API/web builds, scripts and Next typechecks, and diff checks passed. GUI retest skipped as authorized based on the colleague's prior testing.
Preserve the ID fallback and compact editor controls. Add dialog keyboard focus, Tab containment and Escape handling; document navigation and picker behavior.
Validation: 418 tests, API/web builds, scripts and Next typechecks, isolated AMEV dialog checks and diff checks passed. Full GUI retest waived by the user based on the colleague's prior testing; the keyboard follow-up was checked in isolation.
The printed page kept the dark app background in the page margins, the
consumer column wrapped while the empty remark column took the space, an
empty "bearbeitet von" label was printed, and numbers were printed as
typed (11200). Print margins are white now, the consumer column is wider,
empty editors are omitted and numbers print with thousands separators
(11.200) like the template.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Versorgung field suggests ELT and MSR (plus values already used in
the balance) through a datalist and still accepts free text. The circuit
number column is not needed in early design phases; it is removed from
the data model, the regenerated migration 0010, the editor and the
print. All cells are centered except "Verbraucher / Flächen", group
titles and total labels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The print of the rough power balance was plain grey. It now follows the
kbp design of the print view: a title block with office logo, stand,
project, number, owner, address, work stage, date, editor and office;
header row in kbp blue, cost-group rows tinted with a lilac rule, bold
subtotals, the total including reserve as a blue band, colored
deviations in the comparison, Arial, exact print colors, and a footer
with project, stand and "Seite x von y" on every page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In LPH 2/3 there are no circuits yet, but the office needs a power
balance by DIN 276 cost group, as in the Excel template "Leistungsbilanz -
KG400": each trade enters consumers by piece or by area (W/m²) with GLF
and cos phi; the balance sums AV and SV per cost group, adds a reserve and
derives the maximum current.
The module is separate from the circuit structure: own tables
(migration 0010), own folder src/estimate-balance and an import-free
shared calculation used by API and UI. Writes go through project commands
(estimate-balance.insert/.update/.delete with complete documents), so
revision checks and Undo/Redo apply; it is not part of project snapshots
or transfer yet. A comparison endpoint sums the device rows of all circuit
lists by cost group (longest prefix) against the estimate.
The UI adds a sidebar entry, a list of Stände (template KG 400, empty or
copy) and a spreadsheet-like editor with autosave, AMEV picker for the
GLF, totals, reserve, maximum current, comparison table and print layout.
The tests reproduce the template example exactly (143.24 kW, 148.48 kVA,
157.56 kW / 163.33 kVA with 10 % reserve, 235.75 A). See
docs/estimate-balance.md and issue #7.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The distribution-board search also matches board number and location as
soon as the boards carry them (distribution-board info card of
feature/print-view); the fields are optional in the filter, so the branch
works with and without them. Deleting a project device now asks for
confirmation like the other delete actions, since the row action is a
small trash icon.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "Seitenansicht" link in the project header had text only, while the
other header buttons show an icon plus text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The overview now uses the same icon buttons as the circuit list editor:
row actions are icon-only IconButtons (Pencil, Link2, Library, Trash2),
card-header and panel actions show icon plus text, and the circuit-list
link uses the new IconLink. The version history uses Undo2/Redo2, a
chevron toggle and a RotateCcw restore action. Page-header buttons stay
Bootstrap buttons with a lucide icon so the upcoming "Seitenansicht"
button of feature/print-view merges without conflicts and matches them.
The Verteilungen and Projektgeräte cards get a PM-style filter bar:
search plus MultiSelectDropdown (checkbox list with "Alle", closes on
outside click and Escape) and "Zurücksetzen" while a filter is active.
Only the bar collapses; the state is kept per card in localStorage and a
collapsed active filter shows "Filter aktiv". The header row shows
"x von y", and an empty-state row explains when filters hide everything.
The filter logic is a pure module (overview-filters.ts) with node tests;
selections that are no longer offered (deleted floor) are ignored.
IconButton now imports React explicitly like the other components that
are rendered in node tests with the classic JSX runtime.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Removing the shadow-sm classes touched the page markup of the project
overview, which other branches change as well. The card rule now sets
Bootstrap's --bs-box-shadow-sm to the card shadow instead, so cards with
and without shadow-sm look the same and the pages stay as they are on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Cards had a 3px petrol bar on the top edge, and Bootstrap's shadow-sm
class overrode their own shadow with !important. They now use the PM app
surface: a 1px border all around, a 16px radius and a soft diffuse shadow
with a faint inner highlight, with a stronger shadow and a weaker highlight
in dark mode. The shadow-sm classes are removed from the cards so the
shared rule applies. Menus, drawers and dialogs keep their shadows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The selected GZF cell shows a small table-icon button, and Alt+ArrowDown opens
the same picker from the selected or edited cell; Escape closes it. The
shared GzfPickerModal is reused. The picked mean value is committed through
commitEdit with an explicit target cell, so it is a regular undoable edit on
device rows, compact circuits, reserve circuits and the -frei- row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The arrow next to the "Stromkreisliste" heading returns to the project's
distribution board overview.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
- 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>
- Replace the "Filtern" text buttons with a funnel icon next to the column
label; an active filter is highlighted and shows its value count.
- Header labels no longer break inside words (e.g. "Bemerkung"); hyphens
are non-breaking and a trailing unit moves to its own line, so all
headers share one height.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New IconButton/IconButtonSeparator component based on lucide-react.
- Toolbar: icon plus text, grouped into history, view and insert blocks.
- Group headers: compact icons for add circuit, upstream fuse (VS), group
RCD (FI), edit, move up/down, renumber and delete; the header no longer
overflows the grid.
- Rows: add device, delete circuit (trash) and delete device (ListX).
- The previous German labels remain accessible names and tooltips;
destructive actions highlight in the danger color.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.