Compare commits

...
Author SHA1 Message Date
ajnart bfb834e087 revert: drop Umami concurrency change 2026-08-22 09:06:11 +02:00
ajnart 7a56a7d74f refactor: minimize review fixes 2026-08-22 09:04:17 +02:00
ajnart 396b78aa3e revert: keep only minimal review fixes 2026-08-22 09:00:57 +02:00
ajnart caf1f6621a Merge remote-tracking branch 'origin/release/v2' into feat/fix-v2-stack-review-findings 2026-08-22 08:57:25 +02:00
ajnart dfa32c6add revert: trim optional review fixes 2026-08-22 08:37:13 +02:00
ajnart 5522378ef2 fix: restore fast gate after release sync 2026-08-21 19:14:12 +02:00
ajnart e1775e524b Merge origin/release/v2 into feat/fix-v2-stack-review-findings 2026-08-21 19:08:02 +02:00
ajnart 1b544f599c fix: address review findings in the v2 stack fix PR
Board scaling: no sideways scrolling
- Revert the pannable narrow canvas. The canvas fits the viewport again and
  `.viewport` goes back to `overflow: clip`, so a board can never widen the
  document or scroll horizontally.
- Fix the unreadable-narrow-board regression at the real cause instead: cap the
  inverse UI compensation at 2x (50% canvas scale). Down to 50% text and
  controls keep their physical size; below it the board zooms out as a whole, so
  a 200x200 logical tile hosts at most 28px logical text rather than 96px and
  content stops overlapping its neighbours.

Touch editing
- Add `touch-action: none` to `.board-grid-entry`, the dnd-kit draggable and its
  own drag handle. The 200ms touch activation delay is useless if the browser
  has already handed the gesture to page panning and emitted `pointercancel`.
  Only edit mode renders this element.

Custom widgets
- Memoize the merged `data`/`status`/`options` passed to `CustomJsxRenderer`.
  They were rebuilt every render, so the compiled-template memo never hit and
  the new reference-identity boundary key changed on every render, resetting the
  error boundary and forcing an extra reconciliation of the widget subtree.
- Keep the cache key when the shared Redis cache is unavailable so the
  process-local cache still absorbs polling instead of sending every widget
  refresh upstream.
- Reclaim the shared cache's LRU/size/byte bookkeeping for TTL-expired entries
  and touch the LRU in the same script, so expired ghosts stop consuming the
  64 MiB budget and a read costs one round trip instead of two.

Forwarded IP
- Restore `ipAddressFromHeaders` to the full forwarding chain and add
  `trustedIpAddressFromHeaders` for the last hop. Repointing the shared helper
  had collapsed MCP failed-auth rate limiting into a single bucket for every
  client behind a reverse proxy and replaced the client IP with the proxy IP in
  API-key auth logs.

Runtime and restore
- Make the new /appdata ownership migration best-effort. Under `set -e` a mount
  that rejects chown (CIFS/SMB, fixed-uid NFS) turned into a container that
  refuses to start, and /appdata was never chowned before.
- Report malformed metadata.json as such instead of blaming the archive.
2026-08-21 01:40:46 +02:00
ajnart 496fa60b5f Merge remote-tracking branch 'origin/feat/fix-v2-stack-review-findings' into feat/fix-v2-stack-review-findings
# Conflicts:
#	apps/docs/docs/management/assistant.mdx
2026-08-20 16:35:25 +02:00
ajnart 5fb44a49e3 Merge remote-tracking branch 'origin/release/v2' into feat/fix-v2-stack-review-findings
# Conflicts:
#	apps/docs/docs/management/assistant.mdx
2026-08-20 16:34:48 +02:00
Thomas 4faf81844a Merge branch 'release/v2' into feat/fix-v2-stack-review-findings 2026-08-20 16:26:51 +02:00
ajnart 19274cba54 fix: preserve restore and setup errors 2026-08-20 15:41:48 +02:00
ajnart cf624e2f23 fix: address coderabbit review feedback 2026-08-20 15:20:21 +02:00
ajnart 17aef0588f fix: keep custom widget limits client safe 2026-08-20 14:59:18 +02:00
ajnart ca20431d84 fix: address final review findings 2026-08-20 14:48:02 +02:00
ajnart c732c5045d docs: align v2 fix behavior 2026-08-20 13:08:54 +02:00
ajnart 263702210a fix: address v2 stack review findings 2026-08-20 13:08:54 +02:00
2 changed files with 19 additions and 8 deletions

No files matched your search

@@ -93,8 +93,7 @@ export const LayoutSettingsContent = ({ board, form, isSaving, saveSettingsAsync
setResettingLayoutId(layout.id);
void (async () => {
try {
const savedValues = form.isDirty() ? await saveSettingsAsync() : form.values;
const canonicalLayout = savedValues ? findCanonicalLayout(savedValues.layouts, layout) : undefined;
const canonicalLayout = board.layouts.find((candidate) => candidate.id === layout.id);
if (!canonicalLayout) return;
await resetLayout({ boardId: board.id, layoutId: canonicalLayout.id });
@@ -1,7 +1,7 @@
"use client";
import type { ReactNode } from "react";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import {
Badge,
Box,
@@ -58,6 +58,10 @@ export default function MediaMissingWidget({
});
const data = getUsableWidgetQueryData(mediaQuery);
const [selectedTab, setSelectedTab] = useState<MediaMissingTab>(showMissing ? "missing" : "queued");
const tabScrollPositionsRef = useRef<Record<MediaMissingTab, { x: number; y: number }>>({
missing: { x: 0, y: 0 },
queued: { x: 0, y: 0 },
});
const activeTab = resolveMediaMissingTab(selectedTab, showMissing, showQueued);
useEffect(() => {
@@ -92,10 +96,18 @@ export default function MediaMissingWidget({
const tabLabel = (label: string, shown: number, total: number) => (isThin ? total : `${label} (${shown}/${total})`);
const renderPanel = (
tab: MediaMissingTab,
entries: { item: MissingMediaItem | QueuedMediaItem; integrationId: string }[],
emptyLabel: string,
) => (
<ScrollArea h="100%" scrollbarSize={4}>
<ScrollArea
h="100%"
scrollbarSize={4}
startScrollPosition={tabScrollPositionsRef.current[tab]}
onScrollPositionChange={(position) => {
tabScrollPositionsRef.current[tab] = position;
}}
>
<Box p="xs">
{entries.length === 0 ? (
<Text size="sm" c="dimmed" ta="center" py="md">
@@ -146,7 +158,7 @@ export default function MediaMissingWidget({
{tabLabel(t("tab.missing"), missing.length, missingCount)}
</Text>
</Group>
<Box h="calc(100% - 40px)">{renderPanel(missing, t("empty.missing"))}</Box>
<Box h="calc(100% - 40px)">{renderPanel("missing", missing, t("empty.missing"))}</Box>
</Paper>
)}
{showQueued && (
@@ -157,7 +169,7 @@ export default function MediaMissingWidget({
{tabLabel(t("tab.queued"), queued.length, queuedCount)}
</Text>
</Group>
<Box h="calc(100% - 40px)">{renderPanel(queued, t("empty.queued"))}</Box>
<Box h="calc(100% - 40px)">{renderPanel("queued", queued, t("empty.queued"))}</Box>
</Paper>
)}
</SimpleGrid>
@@ -202,12 +214,12 @@ export default function MediaMissingWidget({
{showMissing && (
<Tabs.Panel value="missing" flex={1} style={{ overflow: "hidden" }}>
{activeTab === "missing" && renderPanel(missing, t("empty.missing"))}
{activeTab === "missing" && renderPanel("missing", missing, t("empty.missing"))}
</Tabs.Panel>
)}
{showQueued && (
<Tabs.Panel value="queued" flex={1} style={{ overflow: "hidden" }}>
{activeTab === "queued" && renderPanel(queued, t("empty.queued"))}
{activeTab === "queued" && renderPanel("queued", queued, t("empty.queued"))}
</Tabs.Panel>
)}
</Tabs>