Wire the operator Alarm Summary page to the transient per-site live alarm
cache (ISiteAlarmLiveCache, T4). Live-cache-first: on site select the page
subscribes and rebuilds rows/rollup from near-real-time onChanged deltas; the
15s poll is kept untouched as the authority for NotReporting and as the
safety net whenever the cache is not live (pre-seed or degraded stream). Both
paths mutate shared state only via the Blazor dispatcher, so they never race,
and each rebuild is an idempotent snapshot.
- IAlarmSummaryService.BuildFromLiveAlarms: flattens a live AlarmStateChanged
snapshot to AlarmSummaryRows with the same deterministic instance-then-name
sort as GetSiteAlarmsAsync; NotReporting always empty on the live path.
- AlarmSummary.razor: inject ISiteAlarmLiveCache; subscribe on select,
re-subscribe on site change, unsubscribe on leave + Dispose.
- Tests: service BuildFromLiveAlarms flatten/sort/empty; page subscribe,
poll-fallback render, onChanged rebuild, unsubscribe on leave/change/dispose.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
Adds 30d (720h) and 90d (2160h) toggle buttons alongside the existing
24h/7d controls on all four KPI trend surfaces. No fetch-logic changes:
the setters already recompute fromUtc and re-fetch, and the query service
(T5) transparently routes windows > RollupThresholdHours to hourly rollups
— so these windows are what actually exercise the rollup path. bUnit tests
extend the Audit and SiteCalls trend harnesses to cover the new buttons.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
GetSeriesAsync now branches on window width: windows wider than
RollupThresholdHours (default 168h/7d) read pre-aggregated hourly rollups
via GetHourlySeriesAsync; windows at or below the threshold read raw
samples via GetRawSeriesAsync (preserving intra-minute detail on 24h/7d).
The boundary is strict greater-than, so exactly 168h stays on the raw
path. KpiSeriesBucketer.Bucket runs unchanged on whichever series (a 90d
rollup can still exceed the point ceiling). Both ctors route identically
via a shared FetchSeriesAsync helper reading _options.RollupThresholdHours,
the same options instance that already supplies DefaultMaxSeriesPoints.
GetSeriesAsync public signature unchanged.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
The Secured Writes page embeds NodeBrowserDialog (@inject IBrowseService) for
the OPC UA tag picker; bUnit resolves the injected service at render time, so
three SecuredWritesTests failed with 'no registered service of type
IBrowseService'. Register a Substitute.For<IBrowseService>() singleton in the
fixture (mirrors NodeBrowserDialogSearchTests). Suite now 7/7 green.
Marks the PLAN-07 'Post-plan known issues' entry and the 00-MASTER-TRACKER
registry cross-reference RESOLVED.
Fully suppress search UI (box + results panel) when ShowSearch=false, and
reset search state on ShowAsync so a reused dialog never shows stale results.
The templates tree rendered a derived/composed member (e.g. LeftReactorSide,
derived from ReactorSide) as a flat leaf, omitting compositions it inherits
from its base (e.g. LeakTest composed onto ReactorSide). BuildCompositionLeavesFor
recursed only over a template's OWN composition rows; an inherited composition
row lives on the ancestor, and TemplateComposition has no IsInherited placeholder
(unlike attributes/alarms/scripts/native-sources), so the child's own Compositions
was empty. Same 'derived templates don't surface inherited members' family as
followups #1/#2, but for compositions. Deploy/flatten was always correct
(TemplateResolver.ResolveAllMembers walks the chain) — display-only.
Fix:
- BuildCompositionLeavesFor now renders the EFFECTIVE composition set (own +
inherited) via EffectiveCompositionsFor, which walks the inheritance chain
(leaf->root, child wins on InstanceName), mirroring the resolver.
- Inherited slots are flagged (TemplateTreeNode.IsInherited), badged 'inherited'
in the label, and their context menu offers only 'Open composed template'
(Rename/Delete edit the ancestor's slot, so suppressed on inherited nodes).
- The same inherited row can appear under several derived members (LeakTest under
both LeftReactorSide and RightReactorSide), so composition nodes use a
path-qualified KeyOverride to keep TreeView selection/expansion keys unique;
recursion is cycle-guarded.
Tests: +1 bUnit (TemplatesPageTests.Renders_InheritedComposition_UnderDerivedComposedMember);
CentralUI suite 867 green; full solution builds 0/0.
Docs: Component-CentralUI.md (effective composition set in tree); known-issues
tracker #9 recorded + resolved.
Note: CentralUI change — shows on wonder-app-vd03 only after that host is redeployed.
#3 — CollisionDetector counted a derived template's IsInherited placeholder
rows as a distinct origin from the parent members the inheritance walk
re-adds, reporting a spurious "Naming collision" for every inherited row and
blocking any attribute/composition add to a derived template. CollectDirectMembers
now skips IsInherited rows on the direct-template and inherited-parent walks;
it keeps them for the composed-module walk, where placeholders are the sole
representation of a derived module's inherited members (that walk does not
climb the composed template's parent chain).
#7 — SandboxAttributeAccessor (Central UI Test-Run host) omitted
WriteBatchAndWaitAsync / WaitAsync / WaitForAsync, so the editor false-flagged
valid instance scripts with CS1061 even though `template validate` and the
deploy gate accept them. Added the five overloads mirroring the runtime
AttributeAccessor; they throw a labelled ScriptSandboxException if run in
Test Run (the central sandbox has no device-batch / event-waiter transport).
Tests: +3 CollisionDetector unit + 1 end-to-end TemplateService (derived add
now succeeds); +2 ScriptAnalysisService diagnose-clean. Each new test verified
to fail without its fix with the exact user-facing symptom. Full suites green
(TemplateEngine.Tests 438, CentralUI.Tests 866).
Docs: Component-TemplateEngine.md (inherited-placeholder collision rule),
Component-ScriptAnalysis.md (third sandbox surface + its compile-clean guard),
known-issues tracker #3/#7 marked resolved and the minor note promoted to #8.
Resolves InboundAPI-026/027/028/029 (+ newly-surfaced -030).
- 026: authorize the scoped Database helper in the design doc; SQL-injection
protection is parameter binding (values never concatenated); allow writes via
ExecuteAsync; drop the false 'read-only' claim. Named connections only.
- 027: async ADO.NET end-to-end (no .GetAwaiter().GetResult()); honour the method
deadline token on ExecuteScalarAsync/ExecuteReaderAsync/ExecuteNonQueryAsync +
a CommandTimeout backstop derived from the method timeout.
- 028: negative-path tests (null-gateway, deadline cancellation, parameterization)
+ e2e Database + WaitForAttribute cases through the real endpoint.
- 029: WaitForAttribute is bounded by its WAIT timeout (per-wait CTS + client-abort
+ explicit token), NOT the method deadline (spec §6) — a long wait may outlive the
method timeout; WithRequestAborted threads the raw client-abort token separately.
- 030: Central UI compile-surface mirrors (InboundScriptHost / SandboxInboundScriptHost)
gained the Database member (drifted since the runtime helper was added) so the
authorized async API type-checks at the design-time gate.
Code-review finding UI-Med-2: the design doc + delivery adapter treat FromNumber and
MessagingServiceSid as either-or, but the entity ctor, EF schema, UI and CLI all hard-
required FromNumber — so a Messaging-Service-only Twilio config (a normal production
setup) could not be created. Bring the implementation into line with the spec:
- Commons: SmsConfiguration.FromNumber -> string? (ctor fromNumber optional);
UpdateSmsConfigCommand.FromNumber -> string?.
- ConfigurationDatabase: FromNumber.IsRequired(false) + migration SmsFromNumberOptional
(ALTER COLUMN nullable, idempotent; Down backfills '' — harmless, MsgSid keeps it
deliverable) + regenerated model snapshot.
- Transport: SmsConfigDto.FromNumber -> string? (round-trips a Messaging-Service-only config).
- CentralUI: form validation requires AccountSid + at-least-one-of(FromNumber, MsgSid);
nullable create/edit paths; From-number help text.
- CLI: --from-number no longer Required; BuildUpdateSmsConfigCommand validates the either-or.
- Adapter: From branch null-forgiving (guarded by the existing incomplete-config check).
Tests: ManagementActor MsgSid-only persists null FromNumber; CLI MsgSid-only builds +
neither-throws + contract (--from-number not Required); CentralUI MsgSid-only save.
- Root context menu now has tabindex/focus + Escape-key close (OnRootMenuKeyDown) mirroring the node menu
- Opening root menu calls _tree.DismissNodeContextMenu(); opening node menu fires OnNodeContextMenuOpened → DismissRootContextMenu so only one menu is ever visible
- Add FolderContextMenu_MoveUp_IsDisabled_OnFirstSibling and FolderContextMenu_MoveDown_IsDisabled_OnLastSibling bUnit tests