Verified project intelligence for writing code: API quirks, platform behavior, and approaches that already failed. Read before working on the subsystem it covers. Feed new entries through the self-improvement protocol (AGENTS.md M5) when a session learns a durable project fact the hard way. Entries carry no personal data, hostnames, or addresses. If an entry stops matching reality, fixing it is a protocol change like any rule edit.
- Events index filters:
Id IN (...)works; theId:csvform does not.Tags.Idaccepts a single value only and cannot combine withId IN. RepeatingMonitorIdparams ORs them. Filter URLs cap out near 8KB; batch long id lists. monitors.jsonreturns the full monitor row to any account that can view the monitor:Path,User,Pass,ONVIF_Password,Options. There is no per-field ACL, so an unprivileged account reads every camera's credentials straight from the API. Nothing app-side can prevent that; the app's job is to not widen it by logging or displaying those values (refs #307).- A camera password lives inside
Pathas URL userinfo (rtsp://user:pass@host/stream), and pre-1.38 servers have no other field for it.lib/security/url-credentials.tsis the only place that knows how to find it; both the log sanitizer and the monitor settings UI go through it. - Alarm state (
monitors/alarm/id:{id}/command:status) comes from the motion score alone, never from recording mode. ARecording=Alwaysmonitor with an opencause: Continuousevent still reports0(IDLE), verified against 1.39.18; it reports ALARM/ALERT on motion like any other monitor.TAPE(4) existed only before its removal in 1.37 dev, where a continuous recorder sat in it while quiet. So "always recording" never means "always alarming", andisAlarmingStatecovers the legacy case already. - Event Server v7.0.22 and later always sends a real
eidin pushes. The historical fake-eid bug (aDate.now()value where an event id belongs) was app-side tray handling, not the ES.
- Multipart MJPEG renders fine inside
<img>on WKWebView (iOS and macOS) and Chromium. The data-URL rendering workaround exists only for WebKitGTK on Linux (Tauri), where streaming leaks NetworkProcess memory andImageDecoderis absent. Do not extend the workaround to other platforms. - Stream teardown sends
CMD_QUITfor the previous connkey before starting a new stream, on every path: unmount, profile switch, manual retry, and tab-visibility resume. Missing one path leaves stale ZMS processes on the server (ee8a7c9d, bef8c42d, e261e539). - Electron background/occlusion process switches do not fix MJPEG going blank on occluded windows; tried and reverted (69990402). The fix is stream-level reconnect on focus or visibility return (f7a8292e).
- Tauri snapshot thumbnails fetch as blob URLs, or WebKitGTK leaks sockets; same constraint as the MJPEG workaround, separate code path (7e121140).
- iOS video.js fullscreen: CSS overrides cannot reliably intercept the
video.js toggle; native iOS fullscreen is the working approach, and the
capacitor://status banner is the accepted tradeoff (efda381a). - Do not skip HLS on Tauri for CORS: the CORS failure is a dev-mode origin artifact; let video.js try HLS and fall back to ZMS (b4299c59).
videojs-markersis called as a plugin method (player.markers(...)) and initialized once per player instance; per-render re-init breaks markers (d0b251f7). PlayerCMD_QUITteardown also guards React StrictMode double-invoked effects (fe042a14).
react-grid-layoutkeepscompactType: 'vertical'andpreventCollision: false; the other values silently break resize handles, tried and reverted same-day (582b3a85, 1685ff90).- Responsive drag/resize montage editing (phone reorder, tablet targets) was built and reverted for a "use a larger screen" toast; montage editing stays desktop-only by design (90a7e1da). Do not re-attempt without a materially different approach.
- Compact/density-mode CSS overrides scope to the compact-mode container, never bare element or utility selectors; global overrides bled into unrelated views three times (86e7c984, 7e69c0d7, 17613d3e).
- Dedupe, no-credentials-in-URLs, and secure-storage rules live in the
Auth tokens contract. Incidents behind them: independent refresh
triggers double-POSTed and the second 401ed the rotated token,
force-logging the user out (26b9e6a9, 19fb60e1); a refresh token in
?token=leaked into server logs (e1393724); plaintext fallback on secure-store failure became drop-and-re-auth (a2cc647d). Web at-rest crypto is obfuscation, not confidentiality. - A ZoneMinder server with auth disabled returns login success with no
tokens: track
requiresAuthexplicitly instead of deriving freshness from token presence, or no-auth servers refresh-loop forever (cf0d3b8f).
- iOS WKWebView can stop updating
env(safe-area-inset-*)after rotation;main.tsxrecomputes them manually. Do not remove that workaround. - Separately: do not add JS
orientationchangehandlers on iOS (video resume, viewport-meta toggling). They interfere with WKWebView's layout pass and desync safe-area insets; tried and reverted (d1112e17, 54af0cfe). CSS-only rotation fixes are the supported path; HTML5 video pausing on rotation is accepted behavior. - On-device WebLLM crashes iOS WKWebView (about 2GB jetsam limit). It is gated off on iOS; remote Ollama is the supported path there.
- Google Play's native debug-symbols warning for Android builds is inherent to stripped Google dependencies and cannot be cleared.
- React Query v5: disabled queries report
isLoading: false. Gate self-heal and reset effects onisSuccess, never onisLoading. useCurrentProfilereads the settings store reactively and bypasses per-getter fixes; settings coercions belong inmergeProfileSettings(see the Settings contract).- List virtualization of EventListView and Logs with
@tanstack/react-virtualfailed twice (blank rows, stale text). Do not re-attempt without a materially different approach. - The React Compiler lint reports at most one violation per function and
only file-scoped
eslint-disablecomments silence it; fixing one violation can reveal the next on the same function.
- Every monitor on the CI test server has
Controllable: 0, so PTZ is untestable in CI; PTZ verification is manual. - PTZ
HoldButtonmust stop the command on unmount, or a held camera keeps panning.
Model choice, backends, reasoning switches, measured behavior patterns,
and the eval harness live in agents/project/llm-models.md; tool-loop
conduct (grounding, error feedback) lives in the Assistant tool loop
contract. Remaining code-path facts:
- Tool-call markup the parser does not recognize (Hermes XML, bare name/arguments JSON) is a parse failure that triggers self-repair retry; it never renders verbatim as the chat answer (2e28e5fc).
- Regex "call a tool" nudges are English-only by construction; gate them on
ToolContext.localeand give other locales a language-neutral reminder (e74bcb84). - Time windows use copy-interpret-compute (refs #265): the model copies
the user's phrase verbatim,
window-interpreter.tsmaps it to fields,resolveWindowdoes arithmetic. Never regress to direct fills or app-side phrase regexes (deleted twice); the measured why lives inllm-models.md.
- The linux-arm64 job runs under qemu and needs Node 18 with a normalized manual-trigger input; do not bump that job's Node version without re-verifying under emulation (76fb8c0d, 57015c35).