Merge upstream/main into fork (v1.5.0-rc.12)#44
Draft
Copilot wants to merge 1479 commits into
Draft
Conversation
- Remove duplicate TagPrecision type from docker-helpers - Import from canonical tag/precision module in docker-image-details-orchestration - Add unit tests for getNumericTagShape and classifyTagPrecision
- Add toSafeExternalUrl to reject non-http(s) protocols in vuln links - Prefix formula-leading CSV fields with apostrophe to prevent spreadsheet injection
Replace computed reverse with a shallowRef + append-aware cache that reuses the existing array when new entries are appended, avoiding a full copy on every streamed log line.
…ssary re-renders - Filter unknown widget IDs in createDefaultLayoutForBreakpoint instead of crashing - Memoize responsive layouts so unchanged breakpoints preserve referential identity
…n, and hide-pinned filter - Add downloadVulnReport tests for CSV/JSON format selection and early-return guards - Test buildDashboardContainerMetrics updateContainers option isolation - Add unit tests for matchesHidePinnedFilter and filterContainersByHidePinned
…re defaults Update configurationValid in all trigger provider tests to match the new digest-aware simpletitle/simplebody default templates.
- Test warnIfDigestRoutingIsSuppressed early return for unrecognised dispatch reasons - Test handleContainerReportDigest skip when changed is false - Test flushDigestBuffer using current store container when update is still available - Test flushDigestBuffer preserving concurrent buffer replacement during revalidation - Test renderBatchTitle with empty containers array - Test handleContainerUpdateFailedEvent and handleSecurityAlertEvent when container is not in store - Test AgentClient ignoring update-applied with empty string data
…for defensive guards - Test toSafeExternalUrl, cancelIdleCallback flush, SBOM stringify failure, migration sanitize - Export createResponsiveLayoutsMemo for direct unit testing of layout memo - Add v8 ignore annotations for SSR guards, ?? fallbacks, and short-circuit equality chains
The outlined variant used dd-text-muted and dd-border which were nearly invisible in dark themes (2:1 contrast, border darker than background). Bump to dd-text-secondary and dd-border-strong so buttons are findable without hovering.
… views - Add dd-bg-button utility (maps to dd-border-strong token) for a surface that's visible against every background in every theme - Change outlined variant from border+muted-text to filled bg+readable text - Add variant="outlined" to all action buttons in ContainerFullPageTabContent which were missing it (defaulting to invisible muted variant) - Add variant="outlined" to "Update Now" button in both side panel and full-page views for consistency
Only run the backfill migration when crossing from pre-1.5.0 to 1.5.0+, not on every startup after 1.5.0.
…ion module Export getNumericTagShapeFromTransformedTag from tag/precision and re-export from tag-candidates, removing the duplicate implementation.
… access - Add TriggerContainer and TriggerNotificationContainer types to eliminate unsafe Container casts for notification event payloads - Use getContainersRaw() instead of getContainers() for trigger dispatch and digest flush so env vars aren't redacted before template rendering - Fix AgentDisconnectedNotificationEvent kind to exclude agent-reconnect - Add tests for raw store resolution and update-failed batch title template
…onsiveLayouts - Move layout hydration, persistence, breakpoint management, and memo into a dedicated composable for separation of concerns - Cache widget breakpoint bounds in DashboardView computed instead of calling getWidgetBoundsForBreakpoint per GridItem prop binding - Add delegation test verifying useDashboardWidgetOrder imports the new module
Compute toSafeExternalUrl once per vulnerability in a computed property instead of calling it per v-if/v-bind in the template loop.
…and document log viewer cache - Test non-update-available rules show correct trigger summary and help text - Test rollback menu action sets activeDetailTab and selects container - Add explanatory comment for newest-first log viewer append cache
…precision dedup The tag/precision module now imports transform from tag/index, so the auto-mock needs an explicit transform implementation that returns the tag unchanged when no formula is provided.
…nd website The axios npm compromise (March 2026) is resolved; remove the banner and dedicated advisory page.
- Add disabledVariantClasses map so disabled buttons keep their fill/text but drop hover effects that imply interactivity - Add disabled:cursor-not-allowed and disabled:opacity-60 globally
…date Add staleMessage option to executeContainerActionState so update and force-update actions show "Already up to date: <name>" instead of silently succeeding when the container has no pending update.
…obile - Responsive column config hides icon column and labels on small screens - Inline container icon in cell for mobile, tighter padding via scoped CSS - Responsive header padding with sm: breakpoint
- Test visibilitychange and pagehide flush pending layout persistence - Assert Rollback Latest button is disabled when no backups are available
v1.5.0 has not shipped yet, so the remaining RC work belongs in the 1.5 line rather than a post-1.5.0 point release. The scope previously slated for v1.5.1 (scanner decoupling, Grype provider, scanner asset lifecycle, dashboard custom grid, containers table redesign) now moves forward into v1.6.0 alongside notifications and release intel. - README roadmap table: remove `v1.5.1` row and merge its items into `v1.6.0` - apps/web/app/page.tsx: flip v1.5.0 status to `next` (still unreleased), flip the former v1.5.1 card to v1.6.0 and mark `planned`, drop the separate v1.6.0 card
…enabled CORS previously defaulted to `origin: '*'` and merely logged a deprecation warning when enabled without an explicit `DD_SERVER_CORS_ORIGIN`. A wildcard CORS origin on an authenticated API is a real security risk: any browser on any site could read responses on behalf of a logged-in user. Warn-and-continue is not acceptable for this — the default has to be fail-closed. - `getServerConfiguration`: joi schema now requires `cors.origin` when `cors.enabled` is true; the `'*'` default is gone. - `configureCors`: throws immediately when CORS is enabled without a trimmed `DD_SERVER_CORS_ORIGIN`, stopping startup before the middleware attaches. - Keep the existing "CORS is enabled" warning so operators still see a reminder about the surface they just opened up. - Tests updated: the former "warns about wildcard default" case now asserts the init error; two new cases cover the explicit wildcard and explicit trusted-origin paths. Configuration schema test pins the new required-origin behaviour.
…thenticated rate-limit keys Express populates `req.ip` from `X-Forwarded-For` when `trust proxy` is configured — and will populate it from the raw header even when it isn't, depending on middleware order. That means unauthenticated clients could spoof their IP through the header and sidestep per-IP rate limits simply by incrementing the header between requests, so bursty abuse from a single host looked like traffic from thousands of different IPs. - `getIpRateLimitKey` now reads `request.socket.remoteAddress` first and only falls back to `request.ip` when the socket address is absent. Socket-level address comes straight from the TCP peer and cannot be spoofed without actual network control. - `IdentityAwareRateLimitRequestLike` picks up an optional `socket` field so callers and tests continue to typecheck. - New test asserts two requests with different `ip` values but the same socket address collapse to the same rate-limit key.
…road origin fallback `isAllowedAuthorizationRedirect` used to let a redirect through if the target `origin` matched any discovered allowed origin — the full endpoint URL never had to match the authorization server's `authorization_endpoint` metadata. An attacker who could serve a different path under the same origin (shared-tenant IdPs, path confusion on proxies) could steer the redirect to a controlled endpoint. - `isAllowedAuthorizationRedirect` now requires the normalised endpoint to be present in `strictEndpoints`. Empty-strict-endpoints = deny, rather than the old "fall back to origin allowlist" behaviour. - `OidcConfiguration` interface extracted and `Authentication<OidcConfiguration>` generic applied so `validateConfiguration`/`maskConfiguration` carry the concrete type instead of `any`. - Tests: reject-without-endpoint-metadata path added; `redirect should reject authorization redirects when authorization endpoint metadata is missing` replaces the old "allow discovery-origin redirects…" case. `initAuthentication should tolerate startup discovery failure…` now supplies the authorization endpoint so the test actually exercises recovery after the stricter check. - Unused `expectDiscoveryFallbackRedirectPayload` helper removed.
…safe grammar
Lifecycle hooks ran via `execFile(sh, '-c', command)`, so any user-configured command
had access to the full POSIX shell — including `&&`, `||`, `;`, `|`, backticks,
`\$(...)`, redirections, glob expansion, and arbitrary quoting tricks. Even with
DD_HOOKS_ENABLED gating the feature off by default, the docs nudge operators to enable
it on trusted hosts, and someone crafting container labels (or modifying a running
compose file) could smuggle arbitrary commands once hooks were on.
- Add a small whitelist-parser: commands must consist of tokens made of safe
characters (`[A-Za-z0-9_./:@%+=,-]`), single- or double-quoted strings (no unescaped
newlines, no backticks in double quotes, no command substitution), and simple
`\$VAR` / `\${VAR}` expansions. Anything else — metacharacters, subshells,
redirection, unterminated quotes — is rejected with `INVALID_HOOK_COMMAND_MESSAGE`
before the command ever reaches `execFile`.
- Runner runs validation before the DD_HOOKS_ENABLED gate so misconfigured hooks get
a clear "unsupported shell syntax" failure instead of silently skipping.
- New unit tests exercise the accepted grammar (plain args, quoted strings, variable
expansions, nested braces, path arguments) and the rejected cases (operators,
substitutions, unterminated quotes, null bytes, newlines inside quotes).
…ross components
All component base classes (`Component`, `Registry`, `BaseRegistry`, `Authentication`,
`Trigger`, `Watcher`, `Agent`) now accept a `TConfiguration extends BaseConfiguration`
generic so subclasses carry their concrete config interface all the way through
`configuration`, `validateConfiguration`, `maskConfiguration`, and the register/init
paths. Previously every subclass fell back to `Record<string, unknown>` / `any`, which
meant a typo in a config field (`authurl` vs `authUrl`, `clientemail` vs `clientEmail`)
compiled cleanly and silently broke at runtime.
- `Component<TConfiguration>` threaded through the registry. `register()` and
`validateConfiguration()` return the concrete configuration type.
- `Registry<TConfiguration>` and `BaseRegistry<TConfiguration>` re-parameterised; every
provider (ACR, Codeberg, Custom, DHI, DOCR, ECR, Forgejo, GAR, GCR, GHCR, Gitea,
Gitlab, Hub, IBMCR, Mau, Quay, `shared/SelfHostedBasic`) declares its own
`{Provider}RegistryConfiguration` interface and extends the base with it.
- `Authentication<TConfiguration>` + `Basic<BasicConfiguration>` + existing
`Oidc<OidcConfiguration>` (already committed) now consistent.
- `Trigger<TConfiguration>` + all trigger providers (Apprise, Command, Discord, Docker,
Dockercompose, Googlechat, Gotify, Http, Ifttt, Kafka, Matrix, Mattermost, MQTT,
Ntfy, Pushover, Rocketchat, Slack, SMTP, Teams, Telegram) declare concrete config
interfaces. `ContainerUpdateExecutor` tightens `getConfiguration()` to
`{ dryrun?: boolean }`. `SelfUpdateConfiguration` drops its open index signature.
- `Watcher<TConfiguration>` + Docker watcher typed.
- `Agent<AgentConfiguration>` typed; `registerAgents` casts the incoming config to
`AgentConfiguration` at the seam.
- Consumer edges (`api/docker-trigger.ts`, `updates/request-update.ts`) switch from
`configuration?: Record<string, unknown>` to `configuration?: object` so callers
can narrow at the use site without forcing every provider to widen.
- `store/index.test.ts` mocks `./notification-history`; `Trigger.test.ts` inherits the
notification-history mock + mock-clear for the digest revalidation test.
- `Component.typecheck.ts` + new `Agent.typecheck.ts` pin the generic contracts — ts
compiles clean across the app now.
No runtime behaviour changes — this is all types. The payoff is that a future typo in
any provider configuration turns into a compile error at the subclass declaration
instead of a silent runtime null deref.
…odesWhat#298) When the controller forwards a docker / dockercompose update trigger to a remote agent, `AgentClient.runRemoteTrigger` was posting the full `Container` object as the axios request body. The agent's express json parser was capped at 256kb in `a45d533b` (v1.5.0 DoS hardening), and common container payloads have grown across the v1.5 RC cycle (release-notes body, env, labels, image metadata, digest watch config, observability fields). For a `linuxserver/calibre:latest` container with enrichment, the payload exceeds 256kb and express returns HTTP 413 before `triggerApi.runTrigger` ever runs — axios on the controller surfaces it as `Request failed with status code 413`, which the reporter saw as broken container updates on rc.8. For `docker` / `dockercompose` update triggers the agent's handler (`app/api/trigger.ts:172-186`) only dereferences two fields from the posted body: - `container.id` — for `storeContainer.getContainer(id)` (the agent uses its own stored record from there on). - `container.name` — for the `Trigger.isRollbackContainer` guard. Everything else is dead weight. The fix posts `{ id, name }` for the update-trigger types via the existing `REMOTE_UPDATE_TRIGGER_TYPES` set. Notification triggers (smtp, slack, etc.) still receive the full container because their handlers render templates against it. - AgentClient.runRemoteTrigger: construct the posted payload conditionally — minimal for update triggers, full container otherwise. Inline comment documents why. - Regression tests in AgentClient.test.ts: - docker update trigger with 300kb release-notes body posts only `{id, name}` (would previously have 413'd). - dockercompose update trigger behaves the same. - smtp notification trigger keeps posting the full container. Batch path (`runRemoteTriggerBatch`) is only used by notification batching, so it is intentionally left untouched — batched updates don't flow through that endpoint. Fixes: CodesWhat#298
…e Pinned (CodesWhat#293) Hide Pinned used to hide every container classified as pinned (`tagPinned === true`), regardless of whether that container had a pending update. Reporter pinned `grafana/grafana:12.3.2` to wait out an upstream regression and expected to see `12.3.3` appear as an available update. Instead, the Containers view Updates Available grouping, the dashboard Updates Available widget, and the dashboard `updatesAvailable` stat card all filtered Grafana out because `classifyTagPrecision` treats any 3+ numeric-segment semver as `specific` / `tagPinned = true`. Filter was defeating exactly the workflow a temporary pin is for. The classifier is not wrong — `12.3.2` is a specific version. The filter semantics were. Hide Pinned is a decluttering filter; a pinned container with an actionable update is the opposite of clutter. `matchesHidePinnedFilter` now keeps a pinned container visible when its `newTag` is set. Static pinned rows (no pending update) are still hidden. Effect: - ContainersView (useContainerFilters): Grafana-style rows surface. - DashboardView Updates Available widget + stat card + breakdown (useDashboardComputed's `updateContainers`): pinned rows with updates count toward the true total and render in the list. Also addresses the rc.7-only flip-flop reporter observed ("appeared again this morning"). That flip-flop was a separate stored- `tagPrecision` backfill race — rc.8's switch to computed `tagPinned` (`c7ecceef`) already eliminated it, but kept the filter semantics permanent. Without this change, upgrading the reporter to rc.8+ would have pinned Grafana out of the list forever. Tests: - ui/tests/utils/hide-pinned.spec.ts: added "returns true for pinned containers with a pending update (CodesWhat#293)" and extended the filter fixture with a pinned-with-update row that must survive the filter. - ui/tests/views/dashboard/useDashboardComputed.spec.ts: flipped the previously-passing "hides pinned containers from dashboard update widgets" expectation — now asserts both rows render, totalUpdates is 2, and the minor-kind breakdown bucket is 1. - ui/tests/views/DashboardView.spec.ts: equivalent flip on the end-to-end mount — Updates Available stat reads 2 and the Recent Updates widget contains both floating and pinned rows. 290 UI tests across the touched suites pass. Fixes: CodesWhat#293
…nds both emails In batch+digest mode, commit 982b4d7 (rc.7) added an unconditional digest- buffer eviction after every successful batch send AND let the batch path record 'update-available' in the persistent notification history. On the next scan, `handleContainerReportDigest` gated the buffer add on `shouldHandleSimpleContainerReport`, which consults the same 'update-available' history entry — so the gate always returned false and the container was permanently locked out of the digest buffer. The morning digest cron then fired on an empty buffer and logged "nothing to send", which is exactly what begunfx reported on CodesWhat#282 with the 2026-04-17 log. Split the digest channel off as its own notification history kind: - Add `'update-available-digest'` to `NotificationEventKind` so batch-channel and digest-channel dedup are tracked independently in `notifications_history`. - New helper `shouldHandleDigestContainerReport` that mirrors the simple variant but checks the new kind; `handleContainerReportDigest` now uses it and emits a debug log on the silent-skip path (previously invisible — the reporter's log showed pushover logging its `once=true` skip but gmail emitting nothing at all). - `flushDigestBuffer` records `'update-available-digest'` after a successful flush; the batch path keeps recording `'update-available'`; neither stomps on the other. - Remove the digest-buffer eviction in `handleContainerReports` — the digest channel now dedups against its own history instead of relying on a destructive cross-channel eviction, so the doc promise at `content/docs/.../triggers/index.mdx:105` ("both modes simultaneously — immediate batch emails plus a scheduled digest summary") holds. - `seedNotificationHistoryFromStore` seeds both kinds for digest-capable modes, preserving the "fresh restart doesn't spam" property per channel. - `handleContainerUpdateAppliedEvent` clears both kinds so a newly-detected update after an applied one re-fires on both channels. Test coverage: invert the now-incorrect "batch should drain digest buffer" test; add cross-cycle tests (re-buffer when hash changes, skip when hash unchanged), per-channel seed tests, `recordNotification` attribution test, and the silent-skip debug log guard. All 327 Trigger/history tests pass. Fixes: CodesWhat#282
- app/package-lock.json: protobufjs 7.5.4 → 7.5.5 (transitive via dockerode) - e2e/package-lock.json: protobufjs 7.5.4 → 7.5.5 (transitive via artillery) GHSA-xq3m-2v4x-88gg (critical): arbitrary code execution in protobufjs prototype chain. Patched in 7.5.5. Closes Dependabot alerts CodesWhat#104 and CodesWhat#105, and unblocks the qlty pre-push gate on feature/v1.5-rc9. Dependabot alert CodesWhat#103 (follow-redirects) is already resolved in the lockfile (1.16.0 ≥ 1.16.0 patched) — it will auto-close on the next Dependabot rescan after push.
…llback, and trim agent debug payload
Three low-severity code smells surfaced during post-fix verification:
- AgentClient.runRemoteTrigger debug log was serializing the full container
even when the posted payload was trimmed to {id, name} for update
triggers. Moved the log after payload computation and log the actual
payload instead, so debug output matches what went over the wire.
- buildPreloadedActiveOperationLookup returned undefined for empty stores,
silently falling back to the per-row path. Added a comment explaining
the fallback is intentional — the per-row path handles empty stores
correctly on its own, and this keeps the rare case on a known-good path
instead of growing a second empty-map branch.
- buildAutoTriggerErrorSignature keys by watcher + error, not by container.
That looked like an aliasing risk (same-named containers on the same
watcher could share a signature) but is deliberate: a burst of identical
errors from one system-level condition (SMTP down, agent disconnected)
should produce a single warn log, not one per container. Added a comment
recording the intent so future readers don't regress it.
No behavioral changes. All 456 Trigger / AgentClient / list handler tests
still pass.
The Unreleased section had only three entries (CodesWhat#293, CodesWhat#298, CodesWhat#296) while the branch carries ~40 commits since v1.5.0-rc.8. Backfilled the remaining entries grouped by category: - Added: notification history store (persistent once-dedup), OIDC redirect allowlist, SPA + hashed-asset cache-control, label truncation with tooltip directive, bounded native container table scroll. - Changed: generic component configuration types, registry public-image credential fallback hoisted into BaseRegistry, compose bind-mount remap shared between docker triggers, dimmed updating row styling. - Fixed: CodesWhat#282 (batch+digest digest channel split), CodesWhat#293, CodesWhat#298, CodesWhat#296 (already present), Trivy DB cache race, malformed tag-transform regex, SSE teardown double-run, centered updating badge, stack Update All row lockout, z-index utility registration, containers table 70vh cap. - Performance: CodesWhat#301 (preloaded active update operations) and container list Proxy projection. - Security: GHSA-xq3m-2v4x-88gg (protobufjs), GHSA-r4q5-vmmm-2653 (follow-redirects), hook command grammar validator, OIDC strict authorization-endpoint match, OIDC token redaction, rate-limit key via socket.remoteAddress, CORS explicit DD_SERVER_CORS_ORIGIN, Snyk policy scope narrowing. README Recent Updates list refreshed with the six highest-impact new items pulled forward from the Unreleased changelog, keeping the existing rc.8/rc.7 highlights below.
… HookRunner, Trigger, helpers New / expanded tests to bring every touched file to 100% lines/branches/ statements/functions: - list.test.ts: covers the new createProjectionView descriptor override path (writable handling for non-configurable source descriptors) plus the preload-vs-per-row selection in buildContainerListResponse. - BaseRegistry.test.ts: covers the hoisted authenticateBearerFromAuthUrlWithPublicFallback — providerLabel log wording, non-Error rethrow, and the public-credential fallback path. - notification-history.test.ts: parametric test for the new update-available-digest kind (independent of update-available). - update-operation.test.ts: edge cases on listActiveOperations / ACTIVE_STATUSES snapshotting. - helpers.test.ts: exercises the expanded mock request/response helpers so the helper surface itself is covered. - HookRunner.test.ts: covers the simplified parser after dead-branch removal (unterminated single-quote, missing token after operator, variable-reference-without-$ entry point). - Trigger.test.ts: adds coverage for the simplified digest-skip log format plus hasAlreadyNotifiedForResult short-circuit when no containerId is available. - compose-file-sync.test.ts + ComposePathBindMounts.test.ts (new): coverage for the bind-mount remapper that now ships as a shared helper between docker / dockercompose triggers. Source simplifications were needed to make the coverage complete: - HookRunner.ts: removed three unreachable guard clauses (the $ prefix is already checked by the caller, embedded newlines inside single-quoted segments are already rejected by the grammar, and the end-of-string / no-consumed-token branches cannot be reached given the loop's preceding whitespace skip). - Trigger.ts: removed redundant container.updateAvailable && once===true pre-checks from the alreadyBuffered computation (both conditions are already required by shouldHandleDigestContainerReport before the log path is reached) and tightened the log formatting to `once === true` rather than `once ?? false`. - list.ts: dropped the empty-overrides fast-exit in createProjectionView (all call sites pass at least one override) and made the property- descriptor writable determination robust when the source property is non-configurable with a non-writable descriptor. 317 test files, 6851 tests pass; coverage gate holds at 100%.
…d snooze test fixture - ContainersGroupedViews.vue: add max-height="70vh" on the grouped-view DataTable. The grouped (Stacks) view virtualization is off, so the table needs its own scroll bound — without it, the full rendered list pushes the outer page into a secondary scroll, and the sticky header detaches. The flat containers view does NOT get this cap (covered by d2fceec); the two layouts intentionally scroll differently. - ContainersGroupedViews.spec.ts: updated "disables virtualization and bounds native table scrolling" expectation to match. - useContainerPolicy.spec.ts: the snoozed-policy fixture was hard-coded to 2026-04-14, which became a past date on today's runs and broke the "snoozed: true" assertion. Hoisted to a 2099-04-14 constant so the snooze stays in the future indefinitely.
…ine edge Alpine's edge/testing repo advanced past 0.69.3-r2 — the exact-version pin started failing the base image build with `trivy-0.70.0-r0: breaks: world[trivy=0.69.3-r2]`. Bumped the pin to 0.70.0-r0 so the base image can assemble again. Both versions are safe per our Trivy supply chain advisory (we're not affected regardless of version).
…gistration scripts/start-drydock.sh registers two triggers in the E2E instance as of commit 264a1b9 (the dashboard Playwright race-fix): docker.local with AUTO=false, plus the existing mock.example. The old fixtures still asserted "length 1" on /api/triggers and the legacy "No docker trigger found" 404 on /api/containers/{id}/{stop,start,restart}, so the e2e gate was failing on pre-existing drift, not on any new code. - api-trigger.feature: collapse the all-triggers scenario to check minimum length 2 and valid JSON shape. The specific-trigger scenario below (unchanged) is the authoritative contract check for mock.example. - api-v14.feature: flip the lifecycle assertions from 404/error-body to 200/"Container {stopped,started,restarted} successfully". The round- trip leaves the container back in running state, so follow-on scenarios that expect hub_nginx_120 alive stay green. Both feature files now include a comment explaining why the e2e instance carries docker.local, so a future reader doesn't revert the change thinking the legacy fixtures were canonical.
…events On every PR synchronize, both `push` (ref=refs/heads/feature/...) and `pull_request` (ref=refs/pull/<n>/merge) fire the ci-verify workflow. The old concurrency group keyed on `github.ref`, which differs between the two events, so both ran to completion in parallel — ~10 duplicate jobs per push on the rc.9 PR alone. Key the concurrency group on the source branch name instead (`head_ref` on PRs, `ref_name` everywhere else). Both events now land in the same slot, and the existing `cancel-in-progress: true` cancels whichever started first. Main-branch pushes and merge_group events continue to run against their own ref names unchanged.
…once Keeping `feature/**` on `push` meant every PR synchronize fired CI twice — once as the push event and once as the pull_request event. The prior commit (fd14ad0) papered over this via concurrency cancellation, but the duplicate run still started and consumed runner seconds before getting killed. Feature-branch CI now runs only via the `pull_request` trigger: one run per PR event, no cancellation race, no duplicated job graph. Direct pushes to `main` and `release/**` continue to trigger CI via `push` for post-merge and release workflows where there is no PR. Solo feature-branch pushes without an open PR will not run CI in GitHub Actions. The lefthook pre-push gate (biome → qlty → coverage → build → e2e → playwright → zizmor) already runs locally on every push, so the developer-feedback loop is preserved. The concurrency group retains the `head_ref || ref_name` key so rapid successive pushes to the same branch still cancel the in-flight run.
- Agent.typecheck.ts: prefix bare member access with `void` so CodeQL stops flagging the `// @ts-expect-error` probe as a no-effect expression while still triggering the type error the test asserts on. - HookRunner.isAllowedHookCommand: drop the dead `consumedToken` local — its only assignment was unread; the token-required guard is the existing `nextIndex === undefined || nextIndex === index` return on line above. - Trigger.test.ts: add `expect(triggerBatchSpy).toHaveBeenCalled()` assertions in the two batch+digest cross-cycle tests so the spies have an observed effect (CodeQL Useless-variable findings). All 341 tests in the impacted files still pass; biome clean.
QA'd locally against test/qa-compose.yml and tightened a handful of UI density/alignment papercuts that surfaced in the v1.5-rc.9 review. - ContainersGroupedViews: drop the rc.9-reintroduced `max-height="70vh"` cap on the grouped table. DataViewLayout already hosts a page-level scroller; the nested cap was producing a second scrollbar and phantom whitespace under the rows. Flat view never had the cap; this restores parity. - DataTable: default cell vertical alignment switches from `align-top` to `align-middle`. Multi-line name+image cells were pushing rows taller than the actions column, leaving the action buttons floating mid-row while every other cell hugged the top. Centering everything reads as a unified horizontal band of metadata. - ContainersGroupedViews + ContainersView: container icon size 20→32 + icon column width 40px→56px so the icons actually register on dense rows. - AppLayout: sidebar nav `py-3` → `pt-1 pb-3`. Brand-to-first-nav-item gap drops from ~14px to ~6px so the DRYDOCK brand anchors to the nav grid instead of floating above dead space. - NotificationBell: replace per-row `border-bottom` with alternating `var(--dd-bg-card)` / `var(--dd-bg-inset)` backgrounds, matching the zebra-striped pattern every other DataTable in the app uses. Tests updated to match (DataTable.spec.ts, ContainersGroupedViews.spec.ts). All affected unit tests pass; verified visually in qa-compose.
✨ feat(rc): v1.5.0-rc.9 — batch+digest dedup split, CodesWhat#301 list perf, socket-proxy identity, remote-agent 413 fix
…ork (CodesWhat#304) ## Summary - **Security scan digest ([CodesWhat#300](CodesWhat#300 — `SECURITYMODE=digest` (and `batch+digest`) emits one severity-grouped summary per scan cycle instead of one notification per vulnerable container. New `POST /api/v1/containers/scan-all` scans the whole fleet server-side and emits a single `security-scan-cycle-complete` with stable UUID v7 `cycleId`. The UI **Scan All** button now uses the bulk endpoint so a 40-container inventory produces one email instead of forty. - **Notification dropdown rework ([CodesWhat#267](CodesWhat#267 — Bell dropdown moved to GitHub/Linear/Slack consensus layout: per-row ✕ dismiss (hover-reveal desktop, always-on touch), a header **Clear** bulk action, split footer (Mark all as read / Open audit log). New `--dd-zebra-stripe` `color-mix()` token keeps alternate rows legible on every stock theme. - **Actionable deprecation banners ([CodesWhat#214](CodesWhat#214 — All 5 deprecation banners now carry inline migration actions plus a "View migration guide" link that deep-jumps to the relevant anchor. New doc anchors: `#legacy-env-vars`, `#legacy-labels`, `#legacy-trigger-prefix`, `#legacy-password-hashes`, `#curl-healthcheck-override`, `#oidc-http-discovery`, `#unversioned-api-paths`. - **Source project shortcut link ([CodesWhat#295](CodesWhat#295 — Containers render a "View project" link (GitHub/GitLab icon) next to release notes in detail panels/cards when `org.opencontainers.image.source`, `dd.source.repo`, or GHCR-derived source URL is available. - **Watcher next-run absolute-timestamp tooltip ([CodesWhat#288](CodesWhat#288 — Next-update-check column surfaces the absolute local time on hover alongside the live relative countdown. - **Trigger digest flush DRY refactor** — `flushDigestBuffer` / `shouldHandleDigestContainerReport` are now parameterized on event kind, so update-digest and security-digest share one implementation while preserving the rc.9 CodesWhat#282 dedup invariants. - **Coverage gap closures** — +610 lines of new tests across Trigger, container API, bulk-security, AgentClient, scheduler, plus mechanical `securitymode: 'simple'` baseline across all trigger-provider test fixtures. 100% coverage thresholds hold. Full change list: see `CHANGELOG.md` `[1.5.0-rc.10]` section. ## Test plan - [x] Vitest app suite — 100% coverage thresholds - [x] Vitest ui suite — 100% coverage thresholds - [x] Biome lint — 1 non-fatal warning (`useConst` in `bulk-security.test.ts:641` — biome classifies let→const as unsafe auto-fix; fix queued as follow-up) - [x] qlty gate — clean - [x] Cucumber e2e — 48 scenarios / 388 steps pass - [x] Docker image build — succeeds - [ ] Reporter verification on rc.10 once tagged - [ ] CodesWhat#267 — confirm Clear + per-row ✕ + zebra striping read well on their theme - [ ] CodesWhat#300 — confirm digest email groups vulnerabilities by severity and "Scan All" sends one email - [ ] CodesWhat#214 — confirm deprecation banner links deep-jump correctly to migration sections - [ ] CodesWhat#288 — confirm next-run tooltip shows absolute time - [ ] CodesWhat#295 — confirm source-project link renders for watched containers with OCI source labels --------- Co-authored-by: superuserjr <80784472+turbodaemon@users.noreply.github.com>
…dashboard toasts, SSE replay, dashboard UX polish, CodesWhat#311 expand-all stacks (CodesWhat#312) ## Summary Release candidate 11. Mix of bug fixes (update-flow correctness for CodesWhat#289 / CodesWhat#291 / CodesWhat#293-regression-guard) and UX polish on the dashboard and containers views. Full entries in `CHANGELOG.md`. ### Highlights - **CodesWhat#289** — container rows no longer drop sort position mid-update; every terminal state (succeeded / failed / rolled-back) now fires a toast; missed terminal SSEs are self-healing via a reconciliation pass after each list refresh. - **CodesWhat#291** — dashboard update flow now emits the same toast sequence as the Containers view (*"Update started"* → *"Updated / Update failed / Rolled back"*). - **SSE Last-Event-ID replay protocol** — monotonic `<bootId>:<counter>` ids + 5-minute ring buffer + `dd:resync-required` fallback on boot-mismatch or buffer-evicted, replacing the best-effort reconnect. - **Concurrent dashboard update queueing** — per-row Update button is no longer gated by a global bulk-lock. - **Centered Updating/Queued badge on dashboard** — ported the `dd-row-updating` / `.dd-row-overlay` pattern from the Containers view. - **Dashboard row click opens container** — navigates to `/containers?containerIds=<id>`. - **Discussion CodesWhat#311** — Expand/Collapse all stacks toggle in the Containers toolbar (icon flips based on state). - **`resultChanged` preserved through env redaction** — fixes `resultChanged is not a function` in watcher scans. ## Test plan - [x] UI coverage 100/100/100/100 (166 test files, 2664 tests) - [x] App coverage 100/100/100/100 (320 test files) - [x] Pre-push gauntlet green locally: biome / qlty / coverage / build / e2e / e2e-playwright - [x] Manual QA on the QA stack for CodesWhat#289 (row stability + terminal toasts), CodesWhat#291 (dashboard toasts), concurrent queueing, centered badge, row-click navigation, expand/collapse-all - [ ] CI-verify on this PR green - [ ] QA by @begunfx across CodesWhat#289 / CodesWhat#291 / CodesWhat#272 / CodesWhat#311 once RC is cut --------- Co-authored-by: superuserjr <80784472+turbodaemon@users.noreply.github.com>
…hat#309), perf/UX polish, CVE bumps, CI hardening Release branch merge. See CHANGELOG [1.5.0-rc.12] for full details.
…-branch-on-upstream # Conflicts: # .github/workflows/ci.yml # CHANGELOG.md # app/api/component.test.ts # app/api/component.ts # app/api/container.test.ts # app/api/trigger.test.ts # app/api/trigger.ts # app/registry/index.ts # app/triggers/providers/Trigger.test.ts # app/triggers/providers/Trigger.ts # app/triggers/providers/command/Command.test.ts # app/triggers/providers/dockercompose/Dockercompose.test.ts # app/triggers/providers/dockercompose/Dockercompose.ts # app/triggers/providers/gotify/Gotify.test.ts # app/triggers/providers/ifttt/Ifttt.test.ts # app/triggers/providers/kafka/Kafka.test.ts # app/triggers/providers/mqtt/Mqtt.test.ts # app/triggers/providers/ntfy/Ntfy.test.ts # app/triggers/providers/pushover/Pushover.test.ts # app/triggers/providers/slack/Slack.test.ts # app/triggers/providers/smtp/Smtp.test.ts # app/triggers/providers/telegram/Telegram.test.ts # app/watchers/providers/docker/Docker.test.ts # app/watchers/providers/docker/Docker.ts # ui/src/services/registry.ts Co-authored-by: Crow-Control <7613738+Crow-Control@users.noreply.github.com>
Copilot created this pull request from a session on behalf of
Crow-Control
April 23, 2026 12:55
View session
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebases the fork onto upstream
mainata00798b(v1.5.0-rc.12), resolving all conflicts while preserving fork-specific features.Conflict resolution strategy
Dropped from fork (upstream superseded):
requireincludetrigger config field and all associated schema, API-layer enforcement, and tests — upstream removed this concept entirelymustTriggerAPI-layer guard — upstream evolved trigger dispatch differentlyrequireincludepreservation logic inmapComponentToItem(app/api/component.ts)Docker.initWatcher— moved todocker-remote-auth.tsupstreamKept from fork (fork-specific features):
ensureComposeTriggersFromStore,appendTriggerId,removeTriggerId,getComposeFilePathFromLabels, compose-native path resolution, all compose label constantssplitDigestReference,normalizeImageWithoutDigest,buildUpdatedComposeImage, digest-stripped image matchingsanitizeComponentName/CONTAINER_TRIGGER_DEFAULT_NAMEinapp/registry/index.tsgetRegistryProviderName/getRegistryDisplayNameinui/src/services/registry.ts[Unreleased]CHANGELOG section (compose features, MAU registry, TrueForge registry fix)Merged (both sides contributed):
Docker.tsimage-matching: upstream's priority-ordered explicit checks + fork's digest-stripped comparison tiersDocker.tswatch loop: fork'scatchblock retained alongside upstream's newfinallycleanup (endDigestCachePollCycleForRegistries,emitWatcherStop)Docker.test.ts/Dockercompose.test.ts: fork's compose/digest tests appended to upstream's new test suitesDocker.tsexports: fork'sappendTriggerId,removeTriggerId,getComposeFilePathFromLabelsadded to upstream's expanded testable-export list