fix(documents): remove bottom panel localStorage persistence
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

The panel was restoring its open/tab/height state from localStorage,
causing the discussion drawer to reopen on every subsequent page visit
even without a ?commentId= param. Removed all LS_KEY_* constants, the
savedOpen/savedTab/savedHeight restore logic, and the persistence
$effect. The panel now always starts closed (or opens to metadata when
the document has no file yet), and the discussion tab opens exclusively
via the commentId deep-link query param.

Also add .svelte-kit-backup/ to .gitignore and .prettierignore to
prevent lint failures from the root-owned Docker-generated directory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-29 21:06:50 +02:00
parent 5374bdabd4
commit 04d3ac0415
9 changed files with 173 additions and 35 deletions

1
frontend/.gitignore vendored
View File

@@ -6,6 +6,7 @@ node_modules
.netlify
.wrangler
/.svelte-kit
/.svelte-kit-backup
/build
# OS

View File

@@ -9,6 +9,7 @@ bun.lockb
/static/
# Generated files
/.svelte-kit-backup/
/src/lib/generated/
/src/lib/paraglide/
/src/lib/paraglide_bak*/

View File

@@ -0,0 +1,31 @@
import type * as Kit from '@sveltejs/kit';
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
type MatcherParam<M> = M extends (param : string) => param is (infer U extends string) ? U : string;
type RouteParams = { id: string };
type RouteId = '/persons/[id]/edit';
type MaybeWithVoid<T> = {} extends T ? T | void : T;
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
type EnsureDefined<T> = T extends null | undefined ? {} : T;
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
export type Snapshot<T = any> = Kit.Snapshot<T>;
type PageServerParentData = EnsureDefined<import('../../../$types.js').LayoutServerData>;
type PageParentData = EnsureDefined<import('../../../$types.js').LayoutData>;
export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;
type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];
type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;
type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];
type ActionsExport = typeof import('../../../../../../../src/routes/persons/[id]/edit/+page.server.js').actions
export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>
export type ActionData = Expand<Kit.AwaitedActions<ActionsExport>> | null;
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('../../../../../../../src/routes/persons/[id]/edit/+page.server.js').load>>>>>>;
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View File

@@ -5,7 +5,7 @@
"value": "de",
"domain": "localhost",
"path": "/",
"expires": 1808896929.897686,
"expires": 1809337570.90398,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
@@ -15,7 +15,7 @@
"value": "Basic%20YWRtaW46YWRtaW4xMjM%3D",
"domain": "localhost",
"path": "/",
"expires": 1774423330.233039,
"expires": 1774863971.187596,
"httpOnly": true,
"secure": false,
"sameSite": "Strict"

View File

@@ -0,0 +1,6 @@
{
"branch": "feature/68-new-document-file-first",
"commitSha": "53b482c5f24688ca3426d434c832e8d24acfee4c",
"startedAt": "2026-03-27T10:49:23.106Z",
"description": null
}

View File

@@ -0,0 +1,6 @@
{
"branch": "feature/68-new-document-file-first",
"commitSha": "53b482c5f24688ca3426d434c832e8d24acfee4c",
"startedAt": "2026-03-27T10:49:39.204Z",
"description": null
}

View File

@@ -0,0 +1,6 @@
{
"branch": "feature/68-new-document-file-first",
"commitSha": "53b482c5f24688ca3426d434c832e8d24acfee4c",
"startedAt": "2026-03-27T10:51:02.177Z",
"description": null
}

View File

@@ -69,32 +69,16 @@ $effect(() => {
// ── Bottom panel state ────────────────────────────────────────────────────────
const LS_KEY_HEIGHT = 'doc-panel-height';
const LS_KEY_TAB = 'doc-panel-tab';
const LS_KEY_OPEN = 'doc-panel-open';
let panelOpen = $state(false);
let panelHeight = $state(0); // set to full height on mount
let navHeight = $state(0);
let activeTab = $state<DocumentPanelTab>('metadata');
let localStorageRestored = $state(false);
onMount(() => {
navHeight = document.querySelector('header')?.getBoundingClientRect().height ?? 0;
const savedHeight = localStorage.getItem(LS_KEY_HEIGHT);
const savedTab = localStorage.getItem(LS_KEY_TAB);
const savedOpen = localStorage.getItem(LS_KEY_OPEN);
if (savedTab && ['metadata', 'transcription', 'discussion', 'history'].includes(savedTab)) {
activeTab = savedTab as DocumentPanelTab;
}
const topbar = document.querySelector('[data-topbar]');
panelHeight = window.innerHeight - navHeight - (topbar?.getBoundingClientRect().height ?? 0);
if (savedHeight) {
const h = parseInt(savedHeight, 10);
if (!isNaN(h) && h >= 80) panelHeight = h;
}
if (targetAnnotationId) {
// Deep-link into an annotation comment: open the side panel
@@ -103,16 +87,12 @@ onMount(() => {
// Deep-link into a document-level comment: open discussion tab
panelOpen = true;
activeTab = 'discussion';
} else if (savedOpen === 'true') {
panelOpen = true;
} else if (savedOpen === null && !doc?.filePath) {
// No prior state and no file — open to metadata so the panel is immediately useful.
} else if (!doc?.filePath) {
// No file yet — open to metadata so the panel is immediately useful.
panelOpen = true;
activeTab = 'metadata';
}
localStorageRestored = true;
// Track last-visited document for the dashboard resume strip
if (doc?.id) {
localStorage.setItem(
@@ -134,14 +114,6 @@ onMount(() => {
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
});
// Persist panel state whenever it changes (after initial restore).
$effect(() => {
if (!localStorageRestored) return;
localStorage.setItem(LS_KEY_HEIGHT, String(panelHeight));
localStorage.setItem(LS_KEY_TAB, activeTab);
localStorage.setItem(LS_KEY_OPEN, String(panelOpen));
});
</script>
<svelte:head>

View File

@@ -2,6 +2,13 @@
# yarn lockfile v1
"@axe-core/playwright@^4.11.1":
version "4.11.1"
resolved "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.1.tgz"
integrity sha512-mKEfoUIB1MkVTht0BGZFXtSAEKXMJoDkyV5YZ9jbBmZCcWDz71tegNsdTkIN8zc/yMi5Gm2kx7Z5YQ9PfWNAWw==
dependencies:
axe-core "~4.11.1"
"@babel/code-frame@^7.26.2":
version "7.29.0"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz"
@@ -11,11 +18,36 @@
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/helper-string-parser@^7.27.1":
version "7.27.1"
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz"
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
"@babel/helper-validator-identifier@^7.28.5":
version "7.28.5"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz"
integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
"@babel/parser@^7.29.0":
version "7.29.2"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz"
integrity sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==
dependencies:
"@babel/types" "^7.29.0"
"@babel/types@^7.29.0":
version "7.29.0"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz"
integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==
dependencies:
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.28.5"
"@bcoe/v8-coverage@^1.0.2":
version "1.0.2"
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz"
integrity sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==
"@blazediff/core@1.9.1":
version "1.9.1"
resolved "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz"
@@ -181,7 +213,7 @@
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz"
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.31":
version "0.3.31"
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz"
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
@@ -212,6 +244,11 @@
resolved "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.97.tgz"
integrity sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==
"@napi-rs/canvas-linux-x64-musl@0.1.97":
version "0.1.97"
resolved "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.97.tgz"
integrity sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==
"@napi-rs/canvas@^0.1.95":
version "0.1.97"
resolved "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.97.tgz"
@@ -625,6 +662,22 @@
tinyrainbow "^3.0.3"
ws "^8.19.0"
"@vitest/coverage-v8@^4.1.0":
version "4.1.0"
resolved "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.0.tgz"
integrity sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==
dependencies:
"@bcoe/v8-coverage" "^1.0.2"
"@vitest/utils" "4.1.0"
ast-v8-to-istanbul "^1.0.0"
istanbul-lib-coverage "^3.2.2"
istanbul-lib-report "^3.0.1"
istanbul-reports "^3.2.0"
magicast "^0.5.2"
obug "^2.1.1"
std-env "^4.0.0-rc.1"
tinyrainbow "^3.0.3"
"@vitest/expect@4.1.0":
version "4.1.0"
resolved "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz"
@@ -742,6 +795,20 @@ assertion-error@^2.0.1:
resolved "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz"
integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==
ast-v8-to-istanbul@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz"
integrity sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==
dependencies:
"@jridgewell/trace-mapping" "^0.3.31"
estree-walker "^3.0.3"
js-tokens "^10.0.0"
axe-core@~4.11.1:
version "4.11.1"
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz"
integrity sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==
axobject-query@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz"
@@ -1209,6 +1276,11 @@ hasown@^2.0.2:
dependencies:
function-bind "^1.1.2"
html-escaper@^2.0.0:
version "2.0.2"
resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
https-proxy-agent@7.0.6:
version "7.0.6"
resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz"
@@ -1293,6 +1365,28 @@ isexe@^2.0.0:
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.2:
version "3.2.2"
resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz"
integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==
istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz"
integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==
dependencies:
istanbul-lib-coverage "^3.0.0"
make-dir "^4.0.0"
supports-color "^7.1.0"
istanbul-reports@^3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz"
integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==
dependencies:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
jiti@*, jiti@^2.6.1, jiti@>=1.21.0:
version "2.6.1"
resolved "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz"
@@ -1308,6 +1402,11 @@ js-sha256@^0.11.0:
resolved "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.1.tgz"
integrity sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==
js-tokens@^10.0.0:
version "10.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz"
integrity sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
@@ -1433,6 +1532,22 @@ magic-string@^0.30.11, magic-string@^0.30.21, magic-string@^0.30.3, magic-string
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.5"
magicast@^0.5.2:
version "0.5.2"
resolved "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz"
integrity sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==
dependencies:
"@babel/parser" "^7.29.0"
"@babel/types" "^7.29.0"
source-map-js "^1.2.1"
make-dir@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz"
integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==
dependencies:
semver "^7.5.3"
mini-svg-data-uri@^1.2.3:
version "1.4.4"
resolved "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz"
@@ -1598,7 +1713,7 @@ picocolors@^1.0.0, picocolors@^1.1.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
playwright-core@1.58.2:
"playwright-core@>= 1.0.0", playwright-core@1.58.2:
version "1.58.2"
resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz"
integrity sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==
@@ -1755,7 +1870,7 @@ sade@^1.7.4:
dependencies:
mri "^1.1.0"
semver@^7.6.3, semver@^7.7.2, semver@^7.7.3:
semver@^7.5.3, semver@^7.6.3, semver@^7.7.2, semver@^7.7.3:
version "7.7.4"
resolved "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz"
integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==