fix(favorites): a favorite opens the record itself, full width — task page, drawer expand icon, every link a real route
What and why
Handover §4.P (owner feature request + bugs, 2026-10-13): a favorite in the left sidebar must open the record itself, full-width, as a real routed page — not the list the record lives in.
Owner, verbatim (handover §4.P): "a click on a favorite must open the record full-width as a real routed page"; Documents: "open the document page itself (like #/pages/documents/page/<id>) at max width, keeping only the narrow vertical "ALL DOCUMENTS" tree block on the left … This applies to the favorites entry point; normal Documents navigation stays as it is"; Tasks: a favorited task opened #/pages/tasks/dashboard?taskId=<id> and "does NOT open the task"; "Add a tiny "expand to full width" icon to the task sidebar and every similar right-side preview panel: it opens the record as that full-width page with its own URL — bookmarkable and shareable".
This is the first of a few small PRs for §4.P:
- Favorite links take the record (
ENTITY_LINKS[type](id, record)), so a type with more than one full view opens the right one:- Document: a PAGE →
/pages/documents/page/<id>(the document page inside the Documents shell, which keeps only the "All documents" tree beside it); a FOLDER →/pages/documents?folder=<id>; a FILE (no page of its own) and an unknown kind →/pages/documents?id=<id>(hub + preview, handles every kind). Normal Documents navigation is untouched. - Task →
/pages/tasks/view/<id>(new page, below). - Invoice →
/pages/accounting/invoices/view/<id>, or the estimate view/pages/accounting/invoices/estimates/view/<id>when the rowisEstimate(Invoices become favoritable in the follow-up backend PR; the mapping is ready).
- Document: a PAGE →
- Every link target is now a route the app serves. Before, 23 of the 37 link shapes fell through to the
**"not found" page (/pages/organization/vendors/edit/:id,/pages/organization/sprints/edit/:id,/pages/tasks/views/:id,/pages/organization/edit/:id, …). Types with no page of their own yet now land on the closest page that shows them (vendors/teams list, a sprint's project task settings, a module's project, …); the follow-up PRs give the favoritable ones full pages. - Task full-width page
/pages/tasks/view/:id(TaskViewPageComponent, guarded byALL_ORG_VIEW/ORG_TASK_VIEWlike the list): loads the task with the relations its View reads and renders the same sections as the drawer (builder extracted totask-view-sections.ts, header toga-task-view-header), plus the favorite star. Not found (404/400), no permission (401/403) and load error (with Retry) states. - Drawer "expand to full width" icon:
ngx-record-view-drawergets an optionalfullViewLink; when set, the header shows a smallexpand-outlinelink (translated tooltip +aria-label, a real<a routerLink>so it is keyboard-accessible and can be middle-clicked / copied). The task drawer uses it. The other drawers get theirs with their full pages in the next PR. - Shared full-width page frame
ngx-record-view-page(back link, heading, actions slot, loading / not-found / forbidden / error states) — the task page uses it; the next PR's pages reuse it. - Documents star state reads the document id from the sidebar entry id (
favorite-Document-<id>), falling back to every link shape (/page/<id>,?folder=,?id=), so the star still lights up now that pages open the editor. - Contact view route fix:
/pages/contacts/view/:idhad a single child that redirected''totenant, a route that exists nowhere (the component has no<router-outlet>), so every visit — and every contact favorite — ended on "not found". Found by the new route spec.
Decisions (owner asleep; recorded here)
- Estimates stay
Invoicefavorites. An estimate is an invoice row withisEstimate; the link builder reads that flag from the favorite details row and opens the estimate view. No separateEstimateentity type (it would need its own service over the same table and could drift from the invoice row). - Documents FILE favorites keep opening the hub with the preview (
?id=): an uploaded file has no page of its own; only PAGE (and FOLDER) get a different link. - Non-favoritable types (no
@FavoriteServiceon the API, so they can never appear in the sidebar) keep an entry in the exhaustive map, now pointing at an existing page instead of a 404.
Red → green
Red on the unfixed code (feat @ 0002f303342):
entities-mapping.spec.ts Tests: 6 failed, 8 passed, 14 total
● Task › opens the task on its own full-width page, not the dashboard list
● Document › opens a page in the document page (the docs shell keeps only the tree beside it)
● Document › opens a folder as that folder in the hub
● Invoice › opens an invoice in the invoice view
● Invoice › opens an estimate (an invoice row flagged isEstimate) in the estimate view
● Organization opens the record page
favorite-links.routes.spec.ts Tests: 23 failed, 14 passed, 37 total
● … Contact → /pages/contacts/view/<id> ● OrganizationContact → /pages/contacts/view/<id>
● … OrganizationVendor → /pages/organization/vendors/edit/<id> ● TaskView → /pages/tasks/views/<id>
● … OrganizationSprint → /pages/organization/sprints/edit/<id> ● Organization → /pages/organization/edit/<id> (+17 more)
docs-row-actions.spec.ts Tests: 1 failed, 19 passed, 20 total
● favorites › recognises the favorites whose sidebar links open the page editor or a folder
record-view-drawer.component.spec.ts Tests: 2 failed, 1 passed, 3 total
● links the expand icon to the record page, as a real (bookmarkable, middle-clickable) link
● keeps the close button next to it
Green after the change: all of the above plus task-view-page.component.spec.ts (9 tests).
How the route spec works (apps/gauzy/src/app/pages/favorite-links.routes.spec.ts): it builds the real getPagesRoutes() with the plugin routes favorites point into (Documents, Proposals), then for each link loads every lazy loadChildren on the way and reads the module's ROUTES from its injector definition (useValue and useFactory, as the router registers them), and matches the URL with the router's rules (literal / :param segments, '' prefix routes, redirects followed, ** = failure).
Tests and gates run
packages/ui-corejest:entities-mapping.spec.ts14/14,record-view-drawer.component.spec.ts3/3,record-view-markdown.spec.ts12/12.packages/plugins/docs-uijest:docs-row-actions.spec.ts20/20.apps/gauzyjest:favorite-links.routes.spec.ts37/37,task-view-page.component.spec.ts9/9,pages.routes.spec.ts.- Builds:
ui-corelibrary (ng-packagr, AOT) and thegauzyweb app (AOT,dist/apps/gauzy) both compile with this change (local, Node 26).tsc --noEmitof ui-core / docs-ui shows no error in a changed non-spec file. - cspell on every changed file (
favorited,bookmarkableadded to.cspell.json); prettier on every new or previously clean file. - Merged
origin/feat/platform-extensions(beae873df6) and re-ran the suites above. - Browser check on a fresh SQLite demo DB (API :3101, web :4301): see the comment below with screenshots.
Also covers the reviewer note that a favorited vendor opened "not found" (entities-mapping.ts:48, /pages/organization/vendors/edit/:id matched no route): it now opens the vendors page; a vendor view page comes with the follow-up PR that gives the drawer-only records their pages.
🤖 Generated with Claude Code
Summary by cubic
Fixes sidebar favorites so they open the record itself, full width, instead of the list the record lives in, and adds an "expand to full width" link to record drawers that opens the same record page.
- A favorite now opens its own route: a document PAGE opens
/pages/documents/page/:id?view=full(editor as wide as the shell allows, right rail closed, only the "All documents" tree beside it), a FOLDER that folder, an invoice/estimate its matching view, and a task the new/pages/tasks/view/:idpage. - Every link a favorite can produce now matches a real route; previously 23 of 37 shapes fell through to "not found". New route specs resolve each target against the real router configuration, loading every lazy module's
ROUTES. - Invoices, estimates, expenses, income, payments, proposals, equipment and inventory items are now favoritable, and the drawer-only types (vendors, teams, approval requests, recurring expenses, goal KPIs and time frames, merchants, proposal templates) get full-width pages of their own, each guarded like its list.
- The task page renders the same sections as the task drawer (shared builder and header), with loading, not-found, forbidden and retryable-error states. It follows the header's selected organization: switching reloads the page, discards stale loads, and a task of another organization reads "not found" so its favorite star can't file it under the wrong organization.
ngx-record-view-drawergets an optionalfullViewLink; the task drawer shows it as a translated "expand to full width" icon, a real<a routerLink>.ngx-record-view-pageis the shared full-width page frame future record pages reuse.?view=fullapplies the document page's full layout; breadcrumb or tree navigation restores the editor defaults (and only when the parameter had applied them, so the viewer's own toggles on an ordinary page are untouched), while a second favorite re-applies the full layout after the first was toggled away.- The contact view no longer redirects to a nonexistent
tenantroute, which sent every visit to "not found". The Documents star state reads the document id from the sidebar entry id (favorite-Document-<id>), with the link as fallback. - Equipment favorites now list for viewers holding only
ALL_ORG_VIEW, which the equipment pages admit alongsideORG_EQUIPMENT_VIEW.