Adds technical design docs for the navigation epic of the doc-centric pivot: - 207 — Document-centric routing (/docs, /docs/:id?mode=, /index/:store, /runs) - 208 — Doc workspace breadcrumb (Studio > <doc> > <mode>) - 209 — Sidebar nav rework (Home / Docs / Stores / Runs / Settings) - 210 — Feature flag mode gating (deep-link redirect + flag exposure) Status: Accepted on all four. Each doc spells out the contract, alternatives considered, risks per audit dimension, and testing strategy. Backwards-compatibility is preserved throughout: legacy routes and pages keep working until E3/E4/E5 explicitly replace them. Refs #207 #208 #209 #210
7 KiB
Design: Doc workspace breadcrumb
- Issue: #208
- Title on issue: [ENHANCEMENT] Refactor breadcrumb to Studio › ›
- Author: Pier-Jean Malandrino
- Date: 2026-04-29
- Status: Accepted
- Target milestone: 0.6.0 — Doc-centric ingest
- Impacted layers: frontend: shared/ui · app
- Audit dimensions likely touched: Clean Code · Tests · Documentation
- ADR spawned?: no
1. Problem
There is no breadcrumb component today. The user lands on /docs/:id?mode=chunks and has no orientation: which doc, which mode, how to step back. With the new doc-centric URLs introduced in #207, the user navigates between modes on the same doc — a breadcrumb anchors them in the IA.
The shape Studio › <doc title> › <mode> is the agreed pattern: each segment is a link except the last, the doc title is truncated with ellipsis (full title on hover), and the mode segment updates as the user switches tabs without a page reload.
2. Goals
- New
<AppBreadcrumb>component renders three segments on doc workspace pages:Studio › <doc title> › <mode>. - Each non-last segment is clickable:
Studio→/,<doc title>→/docs/:id(default mode). - Doc title truncates beyond ~40 chars with ellipsis; full title shown via
titleattribute on hover. - Mode segment reflects current
?mode=and updates without remount. - On non-doc routes (
/,/runs,/index), the breadcrumb is empty / hidden — no fake breadcrumbs invented. - Component is data-driven: takes a
Crumb[]prop so future routes can supply their own.
3. Non-goals
- Auto-deriving the breadcrumb from the route — for 0.6.0 the doc workspace page passes its crumbs explicitly. Auto-derivation is a follow-up if more pages need it.
- Mobile-specific breadcrumb collapsing — a single-line ellipsis is enough for the layout.
- Breadcrumbs on store / run pages — those land in 0.7.0 with their own page work.
4. Context & constraints
Existing code surface
frontend/src/app/App.vue— the shell. The breadcrumb slot will live in the topbar (between the logo and the right-side actions).frontend/src/pages/DocWorkspacePage.vue(created by #207, placeholder). Will be the first consumer.frontend/src/shared/ui/— home for shared UI primitives. The new component lives here.frontend/src/shared/routing/names.ts(created by #207) — typed route names.frontend/src/shared/i18n.ts— strings.
Hard constraints
- TypeScript strict —
Crumbis a discriminated union (link vs leaf). - Accessibility —
<nav aria-label="breadcrumb">+<ol>+aria-current="page"on the leaf. - No CSS framework lock-in — uses existing token classes from
App.vuestyles, no new lib.
5. Proposed design
5.1 Component contract
frontend/src/shared/ui/AppBreadcrumb.vue:
type LinkCrumb = { kind: 'link'; label: string; to: RouteLocationRaw }
type LeafCrumb = { kind: 'leaf'; label: string }
type Crumb = LinkCrumb | LeafCrumb
defineProps<{ crumbs: Crumb[] }>()
The component renders nothing when crumbs.length === 0.
5.2 Doc workspace integration
The doc workspace page (placeholder from #207) builds its crumbs:
const crumbs = computed<Crumb[]>(() => [
{ kind: 'link', label: t('breadcrumb.studio'), to: { name: ROUTES.HOME } },
{
kind: 'link',
label: truncate(doc.value?.filename ?? '...', 40),
to: { name: ROUTES.DOC_WORKSPACE, params: { id: docId.value } },
},
{ kind: 'leaf', label: t(`breadcrumb.mode.${mode.value}`) },
])
A small helper truncate(text, max) in shared/ui/text.ts adds the ellipsis.
5.3 Slot wiring in the shell
App.vue reserves a slot just under the topbar:
<slot name="breadcrumb">
<AppBreadcrumb :crumbs="[]" />
</slot>
For pages that do not opt in (which is most of them in 0.6.0), nothing renders.
The doc workspace page provides its slot via a <teleport to="#breadcrumb-slot"> or — simpler — the App.vue consults a tiny Pinia store useBreadcrumbStore() that pages set via setCrumbs([...]) on mount and clear on unmount. Goes with the "data-driven" goal.
We pick the store approach: zero teleport, plays well with route-level transitions, and the consumer lives in the page (no shell coupling).
5.4 i18n
New keys under breadcrumb.*:
breadcrumb.studiobreadcrumb.mode.askbreadcrumb.mode.inspectbreadcrumb.mode.chunks
6. Alternatives considered
Alternative A — Auto-derive crumbs from the route
- Summary: A central function that maps each
RouteLocationNormalizedto aCrumb[]. - Why not: Doc workspace needs the doc title which is async — the function would need to fetch it. Page-driven is cleaner for now; auto-derivation can layer on top later for static pages.
Alternative B — Use the page meta object (route.meta.breadcrumb)
- Summary: Each route declares a static
meta.breadcrumbarray. - Why not: Same async-doc-title problem. Plus it scatters crumb config across the router.
7. API & data contract
No backend change. No env vars.
Breaking changes
None.
8. Risks & mitigations
| Risk | Audit dimension | Likelihood | Impact | How we notice | Mitigation / rollback |
|---|---|---|---|---|---|
| Pages forget to clear crumbs on unmount → stale breadcrumb on next route | Clean Code | Medium | Low | Visual regression | The useBreadcrumbStore exposes useCrumbs(crumbs) composable that auto-clears on unmount via onBeforeUnmount |
| Long doc titles break the topbar layout | Clean Code | Low | Low | Visual regression | Truncate at 40 chars + CSS text-overflow: ellipsis as a belt-and-braces |
| Accessibility regression | Tests / Documentation | Low | Medium | Audit | <nav aria-label="breadcrumb"> + aria-current="page" on leaf; tested |
9. Testing strategy
Frontend — Vitest
shared/ui/AppBreadcrumb.test.ts— renders nothing when empty; renders 3 crumbs with correct roles;aria-current="page"on leaf.shared/ui/text.test.ts—truncateboundary cases.shared/state/breadcrumbStore.test.ts—setCrumbs/clear;useCrumbscomposable auto-clears on unmount.
Manual QA
- Visit
/docs/abc?mode=ask→Studio › abc.pdf › Ask. - Switch to chunks mode (when E4 lands) → leaf updates without page flicker.
- Visit
/runs→ no breadcrumb visible.
10. Rollout & observability
Release branch
release/0.6.0.
Feature flag
None.
Observability
None.
Rollback plan
Revert; pages stop providing crumbs and the slot stays empty.
11. Open questions
- Should we render the breadcrumb on
/docs/newasStudio › Import? Decision: yes, the page provides 2 crumbs (Studio › Import). Implemented as a tiny tweak in #214.
12. References
- Issue: https://github.com/scub-france/Docling-Studio/issues/208
- Related issues: #207 (routing), #211 (library), #216 (workspace), #218+ (modes)
- ADRs: none planned
- Project docs: Architecture (
docs/architecture.md), Frontend conventions (frontend/CLAUDE.md)