From 13ac8a11f03158c034e6a8905af72599b9ed9b95 Mon Sep 17 00:00:00 2001 From: Pier-Jean Malandrino Date: Thu, 2 Apr 2026 12:44:08 +0200 Subject: [PATCH 1/5] Fix feature flag health check blocked by CORS Use relative /health path through Vite proxy instead of absolute URL to localhost:8000, matching the pattern used by all other API calls. --- frontend/src/features/feature-flags/store.test.ts | 4 ---- frontend/src/features/feature-flags/store.ts | 4 +--- frontend/vite.config.js | 4 ++++ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/src/features/feature-flags/store.test.ts b/frontend/src/features/feature-flags/store.test.ts index 683ed87..aa5f7fb 100644 --- a/frontend/src/features/feature-flags/store.test.ts +++ b/frontend/src/features/feature-flags/store.test.ts @@ -7,10 +7,6 @@ vi.mock('../../shared/api/http', () => ({ apiFetch: (...args: unknown[]) => mockApiFetch(...args), })) -vi.mock('../settings/store', () => ({ - useSettingsStore: () => ({ apiUrl: 'http://localhost:8000' }), -})) - describe('useFeatureFlagStore', () => { beforeEach(() => { setActivePinia(createPinia()) diff --git a/frontend/src/features/feature-flags/store.ts b/frontend/src/features/feature-flags/store.ts index 3954faf..568609d 100644 --- a/frontend/src/features/feature-flags/store.ts +++ b/frontend/src/features/feature-flags/store.ts @@ -1,7 +1,6 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { apiFetch } from '../../shared/api/http' -import { useSettingsStore } from '../settings/store' type ConversionEngine = 'local' | 'remote' @@ -44,9 +43,8 @@ export const useFeatureFlagStore = defineStore('feature-flags', () => { } async function load(): Promise { - const settings = useSettingsStore() try { - const data = await apiFetch(`${settings.apiUrl}/health`) + const data = await apiFetch('/health') engine.value = data.engine loaded.value = true error.value = null diff --git a/frontend/vite.config.js b/frontend/vite.config.js index a537ebb..9a8b9da 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -9,6 +9,10 @@ export default defineConfig({ '/api': { target: 'http://localhost:8000', changeOrigin: true + }, + '/health': { + target: 'http://localhost:8000', + changeOrigin: true } } } From 102a11765ac6912037aaebfb42e08976747156a1 Mon Sep 17 00:00:00 2001 From: Pier-Jean Malandrino Date: Thu, 2 Apr 2026 13:41:50 +0200 Subject: [PATCH 2/5] Add reusable pagination composable and PaginationBar component Generic usePagination composable with page navigation, size control, and auto-reset on data change. PaginationBar renders prev/next with page size selector. Both support i18n. --- frontend/package-lock.json | 72 ++++++++- .../shared/composables/usePagination.test.ts | 149 ++++++++++++++++++ .../src/shared/composables/usePagination.ts | 71 +++++++++ frontend/src/shared/i18n.ts | 9 ++ frontend/src/shared/ui/PaginationBar.vue | 135 ++++++++++++++++ frontend/src/shared/ui/index.ts | 1 + 6 files changed, 429 insertions(+), 8 deletions(-) create mode 100644 frontend/src/shared/composables/usePagination.test.ts create mode 100644 frontend/src/shared/composables/usePagination.ts create mode 100644 frontend/src/shared/ui/PaginationBar.vue diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f8a34bc..fa8af36 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,7 +8,6 @@ "name": "docling-studio", "version": "0.1.0", "dependencies": { - "@vitest/mocker": "^4.1.2", "dompurify": "^3.3.3", "marked": "^17.0.4", "pinia": "^2.3.0", @@ -19,6 +18,7 @@ "@eslint/js": "^9.0.0", "@types/dompurify": "^3.2.0", "@vitejs/plugin-vue": "^6.0.5", + "@vitest/mocker": "^4.1.2", "eslint": "^9.0.0", "eslint-plugin-vue": "^9.32.0", "prettier": "^3.4.0", @@ -78,6 +78,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "aix" @@ -93,6 +94,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -108,6 +110,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -123,6 +126,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "android" @@ -138,6 +142,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -153,6 +158,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -168,6 +174,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -183,6 +190,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -198,6 +206,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -213,6 +222,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -228,6 +238,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "linux" @@ -243,6 +254,7 @@ "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -258,6 +270,7 @@ "cpu": [ "mips64el" ], + "dev": true, "optional": true, "os": [ "linux" @@ -273,6 +286,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -288,6 +302,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -303,6 +318,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -318,6 +334,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -333,6 +350,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "netbsd" @@ -348,6 +366,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "netbsd" @@ -363,6 +382,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -378,6 +398,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -393,6 +414,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "openharmony" @@ -408,6 +430,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "sunos" @@ -423,6 +446,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -438,6 +462,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -453,6 +478,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -661,6 +687,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -673,6 +700,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -685,6 +713,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -697,6 +726,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -709,6 +739,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -721,6 +752,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -733,6 +765,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -745,6 +778,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -757,6 +791,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -769,6 +804,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -781,6 +817,7 @@ "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -793,6 +830,7 @@ "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -805,6 +843,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -817,6 +856,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -829,6 +869,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -841,6 +882,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -853,6 +895,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -865,6 +908,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -877,6 +921,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -889,6 +934,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -901,6 +947,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "openharmony" @@ -913,6 +960,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -925,6 +973,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -937,6 +986,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -949,6 +999,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -989,7 +1040,8 @@ "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -1307,6 +1359,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", + "dev": true, "dependencies": { "@vitest/spy": "4.1.2", "estree-walker": "^3.0.3", @@ -1372,6 +1425,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "dev": true, "funding": { "url": "https://opencollective.com/vitest" } @@ -1828,7 +1882,7 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -2055,6 +2109,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "dependencies": { "@types/estree": "^1.0.0" } @@ -2099,7 +2154,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=12.0.0" }, @@ -2163,6 +2218,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -2540,7 +2596,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=12" }, @@ -2655,7 +2711,7 @@ "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "devOptional": true, + "dev": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -2797,7 +2853,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "devOptional": true, + "dev": true, "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" @@ -2909,7 +2965,7 @@ "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", - "devOptional": true, + "dev": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/frontend/src/shared/composables/usePagination.test.ts b/frontend/src/shared/composables/usePagination.test.ts new file mode 100644 index 0000000..25009e3 --- /dev/null +++ b/frontend/src/shared/composables/usePagination.test.ts @@ -0,0 +1,149 @@ +import { describe, it, expect } from 'vitest' +import { ref, nextTick } from 'vue' +import { usePagination } from './usePagination' + +function makeItems(n: number): string[] { + return Array.from({ length: n }, (_, i) => `item-${i + 1}`) +} + +describe('usePagination', () => { + it('returns all items when total fits in one page', () => { + const items = ref(makeItems(5)) + const { paginatedItems, pageCount, page } = usePagination(items, { pageSize: 20 }) + + expect(paginatedItems.value).toHaveLength(5) + expect(pageCount.value).toBe(1) + expect(page.value).toBe(1) + }) + + it('slices items according to page size', () => { + const items = ref(makeItems(25)) + const { paginatedItems, pageCount } = usePagination(items, { pageSize: 10 }) + + expect(paginatedItems.value).toHaveLength(10) + expect(paginatedItems.value[0]).toBe('item-1') + expect(pageCount.value).toBe(3) + }) + + it('navigates with next and prev', () => { + const items = ref(makeItems(30)) + const { paginatedItems, page, next, prev } = usePagination(items, { pageSize: 10 }) + + next() + expect(page.value).toBe(2) + expect(paginatedItems.value[0]).toBe('item-11') + + next() + expect(page.value).toBe(3) + expect(paginatedItems.value[0]).toBe('item-21') + + prev() + expect(page.value).toBe(2) + expect(paginatedItems.value[0]).toBe('item-11') + }) + + it('clamps page within valid range', () => { + const items = ref(makeItems(10)) + const { page, prev, next, goTo } = usePagination(items, { pageSize: 5 }) + + prev() + expect(page.value).toBe(1) + + next() + next() + expect(page.value).toBe(2) + + goTo(99) + expect(page.value).toBe(2) + + goTo(0) + expect(page.value).toBe(1) + }) + + it('goTo navigates to a specific page', () => { + const items = ref(makeItems(50)) + const { page, paginatedItems, goTo } = usePagination(items, { pageSize: 10 }) + + goTo(3) + expect(page.value).toBe(3) + expect(paginatedItems.value[0]).toBe('item-21') + + goTo(5) + expect(page.value).toBe(5) + expect(paginatedItems.value[0]).toBe('item-41') + }) + + it('resets to page 1 when items change', async () => { + const items = ref(makeItems(30)) + const { page, next } = usePagination(items, { pageSize: 10 }) + + next() + next() + expect(page.value).toBe(3) + + items.value = makeItems(5) + await nextTick() + expect(page.value).toBe(1) + }) + + it('resets to page 1 when page size changes', async () => { + const items = ref(makeItems(50)) + const { page, next, setPageSize } = usePagination(items, { pageSize: 10 }) + + next() + next() + expect(page.value).toBe(3) + + setPageSize(20) + await nextTick() + expect(page.value).toBe(1) + }) + + it('handles last page with fewer items', () => { + const items = ref(makeItems(23)) + const { paginatedItems, goTo } = usePagination(items, { pageSize: 10 }) + + goTo(3) + expect(paginatedItems.value).toHaveLength(3) + expect(paginatedItems.value[0]).toBe('item-21') + }) + + it('handles empty items', () => { + const items = ref([]) + const { paginatedItems, pageCount, totalItems } = usePagination(items) + + expect(paginatedItems.value).toHaveLength(0) + expect(pageCount.value).toBe(1) + expect(totalItems.value).toBe(0) + }) + + it('exposes totalItems as reactive count', async () => { + const items = ref(makeItems(10)) + const { totalItems } = usePagination(items) + + expect(totalItems.value).toBe(10) + + items.value = makeItems(42) + await nextTick() + expect(totalItems.value).toBe(42) + }) + + it('defaults page size to 20', () => { + const items = ref(makeItems(50)) + const { pageSize, paginatedItems } = usePagination(items) + + expect(pageSize.value).toBe(20) + expect(paginatedItems.value).toHaveLength(20) + }) + + it('ignores invalid page size', () => { + const items = ref(makeItems(10)) + const { pageSize, setPageSize } = usePagination(items, { pageSize: 5 }) + + setPageSize(0) + expect(pageSize.value).toBe(5) + + setPageSize(-10) + expect(pageSize.value).toBe(5) + }) +}) diff --git a/frontend/src/shared/composables/usePagination.ts b/frontend/src/shared/composables/usePagination.ts new file mode 100644 index 0000000..fd7a3f3 --- /dev/null +++ b/frontend/src/shared/composables/usePagination.ts @@ -0,0 +1,71 @@ +import { ref, computed, watch, type Ref } from 'vue' + +export interface PaginationOptions { + pageSize?: number +} + +export interface PaginationReturn { + page: Ref + pageSize: Ref + pageCount: Ref + paginatedItems: Ref + totalItems: Ref + next: () => void + prev: () => void + goTo: (target: number) => void + setPageSize: (size: number) => void +} + +export function usePagination( + items: Ref, + options: PaginationOptions = {}, +): PaginationReturn { + const page = ref(1) + const pageSize = ref(options.pageSize ?? 20) + + const totalItems = computed(() => items.value.length) + + const pageCount = computed(() => Math.max(1, Math.ceil(totalItems.value / pageSize.value))) + + const paginatedItems = computed(() => { + const start = (page.value - 1) * pageSize.value + return items.value.slice(start, start + pageSize.value) + }) + + // Reset to page 1 when source data or page size changes + watch([items, pageSize], () => { + page.value = 1 + }) + + function clamp(target: number): number { + return Math.max(1, Math.min(target, pageCount.value)) + } + + function goTo(target: number): void { + page.value = clamp(target) + } + + function next(): void { + goTo(page.value + 1) + } + + function prev(): void { + goTo(page.value - 1) + } + + function setPageSize(size: number): void { + if (size > 0) pageSize.value = size + } + + return { + page, + pageSize, + pageCount, + paginatedItems, + totalItems, + next, + prev, + goTo, + setPageSize, + } +} diff --git a/frontend/src/shared/i18n.ts b/frontend/src/shared/i18n.ts index 6c4b759..503967e 100644 --- a/frontend/src/shared/i18n.ts +++ b/frontend/src/shared/i18n.ts @@ -106,6 +106,11 @@ const messages: Messages = { 'chunking.chunking': 'Chunking...', 'chunking.chunks': 'chunks', 'chunking.noChunks': 'Lancez le chunking pour préparer les segments.', + 'chunking.noChunksOnPage': 'Aucun chunk sur cette page.', + + // Pagination + 'pagination.pageOf': 'Page {current} sur {total}', + 'pagination.perPage': '/ page', // Settings 'settings.title': 'Paramètres', @@ -207,6 +212,10 @@ const messages: Messages = { 'chunking.chunking': 'Chunking...', 'chunking.chunks': 'chunks', 'chunking.noChunks': 'Run chunking to prepare segments.', + 'chunking.noChunksOnPage': 'No chunks on this page.', + + 'pagination.pageOf': 'Page {current} of {total}', + 'pagination.perPage': '/ page', 'settings.title': 'Settings', 'settings.apiUrl': 'API URL', diff --git a/frontend/src/shared/ui/PaginationBar.vue b/frontend/src/shared/ui/PaginationBar.vue new file mode 100644 index 0000000..9d519f9 --- /dev/null +++ b/frontend/src/shared/ui/PaginationBar.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/frontend/src/shared/ui/index.ts b/frontend/src/shared/ui/index.ts index a8769d5..46da3f7 100644 --- a/frontend/src/shared/ui/index.ts +++ b/frontend/src/shared/ui/index.ts @@ -1 +1,2 @@ export { default as AppSidebar } from './AppSidebar.vue' +export { default as PaginationBar } from './PaginationBar.vue' From ace37429bdced6ec1dbfe780501af2d8816b6d65 Mon Sep 17 00:00:00 2001 From: Pier-Jean Malandrino Date: Thu, 2 Apr 2026 13:41:59 +0200 Subject: [PATCH 3/5] Improve Prepare mode with page filtering, collapsible config, and bbox overlay Filter chunks by current PDF page for consistency with Verify mode. Add collapsible chunking config section and paginated chunk list. Show BboxOverlay in Prepare mode so users see element bounding boxes. Fix scroll containment on prepare panel. --- .../src/features/chunking/ui/ChunkPanel.vue | 130 ++++++++++++++---- frontend/src/pages/StudioPage.vue | 7 +- 2 files changed, 106 insertions(+), 31 deletions(-) diff --git a/frontend/src/features/chunking/ui/ChunkPanel.vue b/frontend/src/features/chunking/ui/ChunkPanel.vue index b93bcde..32551d4 100644 --- a/frontend/src/features/chunking/ui/ChunkPanel.vue +++ b/frontend/src/features/chunking/ui/ChunkPanel.vue @@ -1,10 +1,24 @@