@shipfox/client-shell 12.0.2 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/main-layout.js +2 -2
- package/dist/components/main-layout.js.map +1 -1
- package/dist/components/settings-nav.d.ts +2 -1
- package/dist/components/settings-nav.d.ts.map +1 -1
- package/dist/components/settings-nav.js +17 -13
- package/dist/components/settings-nav.js.map +1 -1
- package/dist/components/workspace-crumb.js +2 -2
- package/dist/components/workspace-crumb.js.map +1 -1
- package/dist/components/workspace-switcher.d.ts.map +1 -1
- package/dist/components/workspace-switcher.js +4 -2
- package/dist/components/workspace-switcher.js.map +1 -1
- package/dist/compose/compose-routes.d.ts.map +1 -1
- package/dist/compose/compose-routes.js +4 -1
- package/dist/compose/compose-routes.js.map +1 -1
- package/dist/compose/validate-registries.d.ts.map +1 -1
- package/dist/compose/validate-registries.js +1 -1
- package/dist/compose/validate-registries.js.map +1 -1
- package/dist/contract.d.ts +2 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js.map +1 -1
- package/dist/core/session.d.ts +1 -0
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js.map +1 -1
- package/dist/hooks/api/session-auth.d.ts.map +1 -1
- package/dist/hooks/api/session-auth.js +1 -0
- package/dist/hooks/api/session-auth.js.map +1 -1
- package/dist/runtime/active-workspace.js +3 -3
- package/dist/runtime/active-workspace.js.map +1 -1
- package/dist/runtime/anchor-paths.d.ts +14 -4
- package/dist/runtime/anchor-paths.d.ts.map +1 -1
- package/dist/runtime/anchor-paths.js +57 -4
- package/dist/runtime/anchor-paths.js.map +1 -1
- package/dist/runtime/anchors.d.ts +9 -6
- package/dist/runtime/anchors.d.ts.map +1 -1
- package/dist/runtime/anchors.js +85 -42
- package/dist/runtime/anchors.js.map +1 -1
- package/dist/runtime/assemble-route-tree.d.ts.map +1 -1
- package/dist/runtime/assemble-route-tree.js +7 -2
- package/dist/runtime/assemble-route-tree.js.map +1 -1
- package/dist/runtime/chrome-context.d.ts +2 -1
- package/dist/runtime/chrome-context.d.ts.map +1 -1
- package/dist/runtime/chrome-context.js.map +1 -1
- package/dist/runtime/compose-client-app.d.ts.map +1 -1
- package/dist/runtime/compose-client-app.js +5 -3
- package/dist/runtime/compose-client-app.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/registries.js +4 -1
- package/dist/runtime/registries.js.map +1 -1
- package/dist/runtime/route-inputs.d.ts +3 -3
- package/dist/runtime/route-inputs.d.ts.map +1 -1
- package/dist/runtime/route-inputs.js +9 -9
- package/dist/runtime/route-inputs.js.map +1 -1
- package/dist/runtime/router-context.d.ts +7 -0
- package/dist/runtime/router-context.d.ts.map +1 -1
- package/dist/runtime/router-context.js.map +1 -1
- package/dist/runtime/search-serialization.d.ts +26 -0
- package/dist/runtime/search-serialization.d.ts.map +1 -0
- package/dist/runtime/search-serialization.js +78 -0
- package/dist/runtime/search-serialization.js.map +1 -0
- package/dist/runtime/workspace-setup.d.ts +1 -0
- package/dist/runtime/workspace-setup.d.ts.map +1 -1
- package/dist/runtime/workspace-setup.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/dist/vite/generate.d.ts.map +1 -1
- package/dist/vite/generate.js +12 -3
- package/dist/vite/generate.js.map +1 -1
- package/package.json +6 -6
|
@@ -7,7 +7,7 @@ import { NavBar } from './nav-bar.js';
|
|
|
7
7
|
import { NavTabs } from './nav-tabs.js';
|
|
8
8
|
export function MainLayout({ navigation, hideProjectNavigation = false }) {
|
|
9
9
|
const workspace = useMaybeActiveWorkspace();
|
|
10
|
-
const {
|
|
10
|
+
const { projectSlug } = useRouteParams(parseWorkspaceProjectParams);
|
|
11
11
|
const matches = useMatches();
|
|
12
12
|
if (!workspace) return /*#__PURE__*/ _jsx(FullPageLoader, {});
|
|
13
13
|
const fullBleed = matches.some((match)=>match.staticData.layout === 'full-bleed');
|
|
@@ -20,7 +20,7 @@ export function MainLayout({ navigation, hideProjectNavigation = false }) {
|
|
|
20
20
|
}),
|
|
21
21
|
hideProjectNavigation ? undefined : /*#__PURE__*/ _jsx(NavTabs, {
|
|
22
22
|
entries: navigation,
|
|
23
|
-
scope:
|
|
23
|
+
scope: projectSlug ? 'project' : 'workspace'
|
|
24
24
|
}),
|
|
25
25
|
fullBleed ? /*#__PURE__*/ _jsx("main", {
|
|
26
26
|
className: "flex-1 min-h-0 flex flex-col overflow-hidden",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/main-layout.tsx"],"sourcesContent":["import {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {Outlet, useMatches} from '@tanstack/react-router';\nimport type {NavTabEntry} from '#contract.js';\nimport {useMaybeActiveWorkspace} from '#runtime/active-workspace.js';\nimport {parseWorkspaceProjectParams, useRouteParams} from '#runtime/route-inputs.js';\nimport {NavBar} from './nav-bar.js';\nimport {NavTabs} from './nav-tabs.js';\n\ndeclare module '@tanstack/react-router' {\n interface StaticDataRouteOption {\n layout?: 'full-bleed';\n }\n}\n\nexport function MainLayout({\n navigation,\n hideProjectNavigation = false,\n}: {\n navigation: readonly NavTabEntry[];\n hideProjectNavigation?: boolean;\n}) {\n const workspace = useMaybeActiveWorkspace();\n const {
|
|
1
|
+
{"version":3,"sources":["../../src/components/main-layout.tsx"],"sourcesContent":["import {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {Outlet, useMatches} from '@tanstack/react-router';\nimport type {NavTabEntry} from '#contract.js';\nimport {useMaybeActiveWorkspace} from '#runtime/active-workspace.js';\nimport {parseWorkspaceProjectParams, useRouteParams} from '#runtime/route-inputs.js';\nimport {NavBar} from './nav-bar.js';\nimport {NavTabs} from './nav-tabs.js';\n\ndeclare module '@tanstack/react-router' {\n interface StaticDataRouteOption {\n layout?: 'full-bleed';\n }\n}\n\nexport function MainLayout({\n navigation,\n hideProjectNavigation = false,\n}: {\n navigation: readonly NavTabEntry[];\n hideProjectNavigation?: boolean;\n}) {\n const workspace = useMaybeActiveWorkspace();\n const {projectSlug} = useRouteParams(parseWorkspaceProjectParams);\n const matches = useMatches();\n if (!workspace) return <FullPageLoader />;\n const fullBleed = matches.some((match) => match.staticData.layout === 'full-bleed');\n const appContentHeight = hideProjectNavigation\n ? '[--app-content-h:calc(100dvh_-_56px)]'\n : '[--app-content-h:calc(100dvh_-_96px)]';\n return (\n <div className=\"h-screen w-full flex flex-col bg-background-subtle-base\">\n <NavBar hideProjectNavigation={hideProjectNavigation} />\n {hideProjectNavigation ? undefined : (\n <NavTabs entries={navigation} scope={projectSlug ? 'project' : 'workspace'} />\n )}\n {fullBleed ? (\n <main className=\"flex-1 min-h-0 flex flex-col overflow-hidden\">\n <Outlet />\n </main>\n ) : (\n <main className={`flex-1 overflow-auto ${appContentHeight}`}>\n <div className=\"max-w-[1120px] mx-auto px-24 py-32\">\n <Outlet />\n </div>\n </main>\n )}\n </div>\n );\n}\n"],"names":["FullPageLoader","Outlet","useMatches","useMaybeActiveWorkspace","parseWorkspaceProjectParams","useRouteParams","NavBar","NavTabs","MainLayout","navigation","hideProjectNavigation","workspace","projectSlug","matches","fullBleed","some","match","staticData","layout","appContentHeight","div","className","undefined","entries","scope","main"],"mappings":";AAAA,SAAQA,cAAc,QAAO,2BAA2B;AACxD,SAAQC,MAAM,EAAEC,UAAU,QAAO,yBAAyB;AAE1D,SAAQC,uBAAuB,QAAO,+BAA+B;AACrE,SAAQC,2BAA2B,EAAEC,cAAc,QAAO,2BAA2B;AACrF,SAAQC,MAAM,QAAO,eAAe;AACpC,SAAQC,OAAO,QAAO,gBAAgB;AAQtC,OAAO,SAASC,WAAW,EACzBC,UAAU,EACVC,wBAAwB,KAAK,EAI9B;IACC,MAAMC,YAAYR;IAClB,MAAM,EAACS,WAAW,EAAC,GAAGP,eAAeD;IACrC,MAAMS,UAAUX;IAChB,IAAI,CAACS,WAAW,qBAAO,KAACX;IACxB,MAAMc,YAAYD,QAAQE,IAAI,CAAC,CAACC,QAAUA,MAAMC,UAAU,CAACC,MAAM,KAAK;IACtE,MAAMC,mBAAmBT,wBACrB,0CACA;IACJ,qBACE,MAACU;QAAIC,WAAU;;0BACb,KAACf;gBAAOI,uBAAuBA;;YAC9BA,wBAAwBY,0BACvB,KAACf;gBAAQgB,SAASd;gBAAYe,OAAOZ,cAAc,YAAY;;YAEhEE,0BACC,KAACW;gBAAKJ,WAAU;0BACd,cAAA,KAACpB;+BAGH,KAACwB;gBAAKJ,WAAW,CAAC,qBAAqB,EAAEF,kBAAkB;0BACzD,cAAA,KAACC;oBAAIC,WAAU;8BACb,cAAA,KAACpB;;;;;AAMb"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SettingsSectionEntry } from '#contract.js';
|
|
2
|
-
export declare function SettingsNav({ entries }: {
|
|
2
|
+
export declare function SettingsNav({ entries, scope, }: {
|
|
3
3
|
entries: readonly SettingsSectionEntry[];
|
|
4
|
+
scope: 'workspace' | 'project';
|
|
4
5
|
}): import("react").JSX.Element | null;
|
|
5
6
|
//# sourceMappingURL=settings-nav.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-nav.d.ts","sourceRoot":"","sources":["../../src/components/settings-nav.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"settings-nav.d.ts","sourceRoot":"","sources":["../../src/components/settings-nav.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,cAAc,CAAC;AAOvD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,GACN,EAAE;IACD,OAAO,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;CAChC,sCA4CA"}
|
|
@@ -2,21 +2,27 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Button } from '@shipfox/react-ui/button';
|
|
3
3
|
import { Icon } from '@shipfox/react-ui/icon';
|
|
4
4
|
import { Link, useMatchRoute } from '@tanstack/react-router';
|
|
5
|
-
import { parseWorkspaceParams, useRouteParams } from '#runtime/route-inputs.js';
|
|
6
|
-
export function SettingsNav({ entries }) {
|
|
7
|
-
const params = useRouteParams(parseWorkspaceParams);
|
|
5
|
+
import { parseWorkspaceParams, parseWorkspaceProjectParams, useRouteParams } from '#runtime/route-inputs.js';
|
|
6
|
+
export function SettingsNav({ entries, scope }) {
|
|
7
|
+
const params = useRouteParams((input)=>scope === 'workspace' ? parseWorkspaceParams(input) : parseWorkspaceProjectParams(input));
|
|
8
8
|
const matchRoute = useMatchRoute();
|
|
9
|
-
if (!params.
|
|
9
|
+
if (!params.workspaceSlug || scope === 'project' && !params.projectSlug) return null;
|
|
10
|
+
const scopedEntries = entries.filter((entry)=>(entry.scope ?? 'workspace') === scope);
|
|
11
|
+
const settingsPath = scope === 'workspace' ? '/w/$workspaceSlug/settings' : '/w/$workspaceSlug/p/$projectSlug/settings';
|
|
12
|
+
const paramsForLink = scope === 'workspace' ? {
|
|
13
|
+
workspaceSlug: params.workspaceSlug
|
|
14
|
+
} : {
|
|
15
|
+
workspaceSlug: params.workspaceSlug,
|
|
16
|
+
projectSlug: params.projectSlug
|
|
17
|
+
};
|
|
10
18
|
return /*#__PURE__*/ _jsx("nav", {
|
|
11
|
-
"aria-label":
|
|
19
|
+
"aria-label": `${scope === 'workspace' ? 'Workspace' : 'Project'} settings`,
|
|
12
20
|
className: "flex flex-col gap-4",
|
|
13
|
-
children:
|
|
14
|
-
const to =
|
|
21
|
+
children: scopedEntries.map((entry)=>{
|
|
22
|
+
const to = `${settingsPath}/${entry.pathSegment}`;
|
|
15
23
|
const active = Boolean(matchRoute({
|
|
16
24
|
to: to,
|
|
17
|
-
params:
|
|
18
|
-
wid: params.wid
|
|
19
|
-
}
|
|
25
|
+
params: paramsForLink
|
|
20
26
|
}));
|
|
21
27
|
return /*#__PURE__*/ _jsx(Button, {
|
|
22
28
|
asChild: true,
|
|
@@ -24,9 +30,7 @@ export function SettingsNav({ entries }) {
|
|
|
24
30
|
className: "w-full justify-start",
|
|
25
31
|
children: /*#__PURE__*/ _jsxs(Link, {
|
|
26
32
|
to: to,
|
|
27
|
-
params:
|
|
28
|
-
wid: params.wid
|
|
29
|
-
},
|
|
33
|
+
params: paramsForLink,
|
|
30
34
|
"aria-current": active ? 'page' : undefined,
|
|
31
35
|
children: [
|
|
32
36
|
/*#__PURE__*/ _jsx(Icon, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/settings-nav.tsx"],"sourcesContent":["import {Button} from '@shipfox/react-ui/button';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Link, useMatchRoute} from '@tanstack/react-router';\nimport type {SettingsSectionEntry} from '#contract.js';\nimport {parseWorkspaceParams
|
|
1
|
+
{"version":3,"sources":["../../src/components/settings-nav.tsx"],"sourcesContent":["import {Button} from '@shipfox/react-ui/button';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Link, useMatchRoute} from '@tanstack/react-router';\nimport type {SettingsSectionEntry} from '#contract.js';\nimport {\n parseWorkspaceParams,\n parseWorkspaceProjectParams,\n useRouteParams,\n} from '#runtime/route-inputs.js';\n\nexport function SettingsNav({\n entries,\n scope,\n}: {\n entries: readonly SettingsSectionEntry[];\n scope: 'workspace' | 'project';\n}) {\n const params = useRouteParams((input): {workspaceSlug?: string; projectSlug?: string} =>\n scope === 'workspace' ? parseWorkspaceParams(input) : parseWorkspaceProjectParams(input),\n );\n const matchRoute = useMatchRoute();\n if (!params.workspaceSlug || (scope === 'project' && !params.projectSlug)) return null;\n const scopedEntries = entries.filter((entry) => (entry.scope ?? 'workspace') === scope);\n const settingsPath =\n scope === 'workspace'\n ? '/w/$workspaceSlug/settings'\n : '/w/$workspaceSlug/p/$projectSlug/settings';\n const paramsForLink =\n scope === 'workspace'\n ? {workspaceSlug: params.workspaceSlug}\n : {workspaceSlug: params.workspaceSlug, projectSlug: params.projectSlug};\n\n return (\n <nav\n aria-label={`${scope === 'workspace' ? 'Workspace' : 'Project'} settings`}\n className=\"flex flex-col gap-4\"\n >\n {scopedEntries.map((entry) => {\n const to = `${settingsPath}/${entry.pathSegment}`;\n const active = Boolean(matchRoute({to: to as never, params: paramsForLink as never}));\n return (\n <Button\n key={entry.id}\n asChild\n variant={active ? 'secondary' : 'transparent'}\n className=\"w-full justify-start\"\n >\n <Link\n to={to as never}\n params={paramsForLink as never}\n aria-current={active ? 'page' : undefined}\n >\n <Icon name={entry.icon} className=\"size-16\" />\n {entry.label}\n </Link>\n </Button>\n );\n })}\n </nav>\n );\n}\n"],"names":["Button","Icon","Link","useMatchRoute","parseWorkspaceParams","parseWorkspaceProjectParams","useRouteParams","SettingsNav","entries","scope","params","input","matchRoute","workspaceSlug","projectSlug","scopedEntries","filter","entry","settingsPath","paramsForLink","nav","aria-label","className","map","to","pathSegment","active","Boolean","asChild","variant","aria-current","undefined","name","icon","label","id"],"mappings":";AAAA,SAAQA,MAAM,QAAO,2BAA2B;AAChD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,IAAI,EAAEC,aAAa,QAAO,yBAAyB;AAE3D,SACEC,oBAAoB,EACpBC,2BAA2B,EAC3BC,cAAc,QACT,2BAA2B;AAElC,OAAO,SAASC,YAAY,EAC1BC,OAAO,EACPC,KAAK,EAIN;IACC,MAAMC,SAASJ,eAAe,CAACK,QAC7BF,UAAU,cAAcL,qBAAqBO,SAASN,4BAA4BM;IAEpF,MAAMC,aAAaT;IACnB,IAAI,CAACO,OAAOG,aAAa,IAAKJ,UAAU,aAAa,CAACC,OAAOI,WAAW,EAAG,OAAO;IAClF,MAAMC,gBAAgBP,QAAQQ,MAAM,CAAC,CAACC,QAAU,AAACA,CAAAA,MAAMR,KAAK,IAAI,WAAU,MAAOA;IACjF,MAAMS,eACJT,UAAU,cACN,+BACA;IACN,MAAMU,gBACJV,UAAU,cACN;QAACI,eAAeH,OAAOG,aAAa;IAAA,IACpC;QAACA,eAAeH,OAAOG,aAAa;QAAEC,aAAaJ,OAAOI,WAAW;IAAA;IAE3E,qBACE,KAACM;QACCC,cAAY,GAAGZ,UAAU,cAAc,cAAc,UAAU,SAAS,CAAC;QACzEa,WAAU;kBAETP,cAAcQ,GAAG,CAAC,CAACN;YAClB,MAAMO,KAAK,GAAGN,aAAa,CAAC,EAAED,MAAMQ,WAAW,EAAE;YACjD,MAAMC,SAASC,QAAQf,WAAW;gBAACY,IAAIA;gBAAad,QAAQS;YAAsB;YAClF,qBACE,KAACnB;gBAEC4B,OAAO;gBACPC,SAASH,SAAS,cAAc;gBAChCJ,WAAU;0BAEV,cAAA,MAACpB;oBACCsB,IAAIA;oBACJd,QAAQS;oBACRW,gBAAcJ,SAAS,SAASK;;sCAEhC,KAAC9B;4BAAK+B,MAAMf,MAAMgB,IAAI;4BAAEX,WAAU;;wBACjCL,MAAMiB,KAAK;;;eAXTjB,MAAMkB,EAAE;QAenB;;AAGN"}
|
|
@@ -10,9 +10,9 @@ export function WorkspaceCrumb({ workspace, compact = false }) {
|
|
|
10
10
|
className: "flex items-center",
|
|
11
11
|
children: [
|
|
12
12
|
/*#__PURE__*/ _jsx(Link, {
|
|
13
|
-
to: "/
|
|
13
|
+
to: "/w/$workspaceSlug",
|
|
14
14
|
params: {
|
|
15
|
-
|
|
15
|
+
workspaceSlug: workspace.slug
|
|
16
16
|
},
|
|
17
17
|
"aria-current": "page",
|
|
18
18
|
className: `inline-block text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors truncate ${compact ? 'max-w-[120px] sm:max-w-[200px]' : 'max-w-[200px]'}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/workspace-crumb.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Popover, PopoverContent, PopoverTrigger} from '@shipfox/react-ui/popover';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport type {Workspace} from '#runtime/auth.js';\nimport {WorkspaceSwitcher} from './workspace-switcher.js';\n\nexport interface WorkspaceCrumbProps {\n workspace: Workspace;\n compact?: boolean;\n}\n\nexport function WorkspaceCrumb({workspace, compact = false}: WorkspaceCrumbProps) {\n const [open, setOpen] = useState(false);\n return (\n <div className=\"flex items-center\">\n <Link\n to=\"/
|
|
1
|
+
{"version":3,"sources":["../../src/components/workspace-crumb.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Popover, PopoverContent, PopoverTrigger} from '@shipfox/react-ui/popover';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport type {Workspace} from '#runtime/auth.js';\nimport {WorkspaceSwitcher} from './workspace-switcher.js';\n\nexport interface WorkspaceCrumbProps {\n workspace: Workspace;\n compact?: boolean;\n}\n\nexport function WorkspaceCrumb({workspace, compact = false}: WorkspaceCrumbProps) {\n const [open, setOpen] = useState(false);\n return (\n <div className=\"flex items-center\">\n <Link\n to=\"/w/$workspaceSlug\"\n params={{workspaceSlug: workspace.slug}}\n aria-current=\"page\"\n className={`inline-block text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors truncate ${compact ? 'max-w-[120px] sm:max-w-[200px]' : 'max-w-[200px]'}`}\n >\n {workspace.name}\n </Link>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Switch workspace\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"ml-2 grid place-items-center size-24 rounded-4 text-foreground-neutral-muted hover:bg-background-components-hover hover:text-foreground-neutral-base transition-colors\"\n >\n <Icon name=\"arrowDownSLine\" className=\"size-16\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[280px] p-0\" align=\"start\" sideOffset={8}>\n <WorkspaceSwitcher activeWorkspaceId={workspace.id} onSelect={() => setOpen(false)} />\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n"],"names":["Icon","Popover","PopoverContent","PopoverTrigger","Link","useState","WorkspaceSwitcher","WorkspaceCrumb","workspace","compact","open","setOpen","div","className","to","params","workspaceSlug","slug","aria-current","name","onOpenChange","asChild","button","type","aria-label","aria-haspopup","aria-expanded","align","sideOffset","activeWorkspaceId","id","onSelect"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,QAAQ;AAE/B,SAAQC,iBAAiB,QAAO,0BAA0B;AAO1D,OAAO,SAASC,eAAe,EAACC,SAAS,EAAEC,UAAU,KAAK,EAAsB;IAC9E,MAAM,CAACC,MAAMC,QAAQ,GAAGN,SAAS;IACjC,qBACE,MAACO;QAAIC,WAAU;;0BACb,KAACT;gBACCU,IAAG;gBACHC,QAAQ;oBAACC,eAAeR,UAAUS,IAAI;gBAAA;gBACtCC,gBAAa;gBACbL,WAAW,CAAC,kJAAkJ,EAAEJ,UAAU,mCAAmC,iBAAiB;0BAE7ND,UAAUW,IAAI;;0BAEjB,MAAClB;gBAAQS,MAAMA;gBAAMU,cAAcT;;kCACjC,KAACR;wBAAekB,OAAO;kCACrB,cAAA,KAACC;4BACCC,MAAK;4BACLC,cAAW;4BACXC,iBAAc;4BACdC,iBAAehB;4BACfG,WAAU;sCAEV,cAAA,KAACb;gCAAKmB,MAAK;gCAAiBN,WAAU;;;;kCAG1C,KAACX;wBAAeW,WAAU;wBAAgBc,OAAM;wBAAQC,YAAY;kCAClE,cAAA,KAACtB;4BAAkBuB,mBAAmBrB,UAAUsB,EAAE;4BAAEC,UAAU,IAAMpB,QAAQ;;;;;;;AAKtF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-switcher.d.ts","sourceRoot":"","sources":["../../src/components/workspace-switcher.tsx"],"names":[],"mappings":"AAeA,wBAAgB,iBAAiB,CAAC,EAChC,iBAAiB,EACjB,QAAQ,GACT,EAAE;IACD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB,+
|
|
1
|
+
{"version":3,"file":"workspace-switcher.d.ts","sourceRoot":"","sources":["../../src/components/workspace-switcher.tsx"],"names":[],"mappings":"AAeA,wBAAgB,iBAAiB,CAAC,EAChC,iBAAiB,EACjB,QAAQ,GACT,EAAE;IACD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB,+BAwDA"}
|
|
@@ -10,6 +10,8 @@ export function WorkspaceSwitcher({ activeWorkspaceId, onSelect }) {
|
|
|
10
10
|
const navigate = useNavigate();
|
|
11
11
|
const setLastWorkspaceId = useSetAtom(lastWorkspaceIdAtom);
|
|
12
12
|
const selectWorkspace = (wid)=>{
|
|
13
|
+
const workspace = workspaces.find((candidate)=>candidate.id === wid);
|
|
14
|
+
if (!workspace) return;
|
|
13
15
|
try {
|
|
14
16
|
setLastWorkspaceId(wid);
|
|
15
17
|
if (user?.id) rememberLastWorkspaceId(user.id, wid);
|
|
@@ -17,9 +19,9 @@ export function WorkspaceSwitcher({ activeWorkspaceId, onSelect }) {
|
|
|
17
19
|
// Local storage is best effort.
|
|
18
20
|
}
|
|
19
21
|
navigate({
|
|
20
|
-
to: '/
|
|
22
|
+
to: '/w/$workspaceSlug',
|
|
21
23
|
params: {
|
|
22
|
-
|
|
24
|
+
workspaceSlug: workspace.slug
|
|
23
25
|
}
|
|
24
26
|
});
|
|
25
27
|
onSelect?.();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/workspace-switcher.tsx"],"sourcesContent":["import {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@shipfox/react-ui/command';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useSetAtom} from 'jotai';\nimport {useAuthState} from '#runtime/auth.js';\nimport {lastWorkspaceIdAtom, rememberLastWorkspaceId} from '#runtime/last-workspace.js';\n\nexport function WorkspaceSwitcher({\n activeWorkspaceId,\n onSelect,\n}: {\n activeWorkspaceId?: string;\n onSelect?: () => void;\n}) {\n const {user, workspaces} = useAuthState();\n const navigate = useNavigate();\n const setLastWorkspaceId = useSetAtom(lastWorkspaceIdAtom);\n const selectWorkspace = (wid: string) => {\n try {\n setLastWorkspaceId(wid);\n if (user?.id) rememberLastWorkspaceId(user.id, wid);\n } catch {\n // Local storage is best effort.\n }\n navigate({to: '/
|
|
1
|
+
{"version":3,"sources":["../../src/components/workspace-switcher.tsx"],"sourcesContent":["import {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@shipfox/react-ui/command';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useSetAtom} from 'jotai';\nimport {useAuthState} from '#runtime/auth.js';\nimport {lastWorkspaceIdAtom, rememberLastWorkspaceId} from '#runtime/last-workspace.js';\n\nexport function WorkspaceSwitcher({\n activeWorkspaceId,\n onSelect,\n}: {\n activeWorkspaceId?: string;\n onSelect?: () => void;\n}) {\n const {user, workspaces} = useAuthState();\n const navigate = useNavigate();\n const setLastWorkspaceId = useSetAtom(lastWorkspaceIdAtom);\n const selectWorkspace = (wid: string) => {\n const workspace = workspaces.find((candidate) => candidate.id === wid);\n if (!workspace) return;\n try {\n setLastWorkspaceId(wid);\n if (user?.id) rememberLastWorkspaceId(user.id, wid);\n } catch {\n // Local storage is best effort.\n }\n navigate({to: '/w/$workspaceSlug', params: {workspaceSlug: workspace.slug}});\n onSelect?.();\n };\n return (\n <Command>\n <CommandInput placeholder=\"Search workspaces...\" />\n <CommandList className=\"max-h-none overflow-visible overflow-y-visible p-0\">\n <div className=\"max-h-300 overflow-y-auto overflow-x-hidden p-4 scrollbar\">\n <CommandEmpty>No workspaces found.</CommandEmpty>\n <CommandGroup heading=\"Workspaces\">\n {workspaces.map((workspace) => (\n <CommandItem\n key={workspace.id}\n value={workspace.id}\n keywords={[workspace.name]}\n onSelect={() => selectWorkspace(workspace.id)}\n >\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${activeWorkspaceId === workspace.id ? 'opacity-100' : 'opacity-0'}`}\n />\n {workspace.name}\n </CommandItem>\n ))}\n </CommandGroup>\n </div>\n <CommandSeparator alwaysRender className=\"mx-0\" />\n <CommandGroup forceMount className=\"p-4\">\n <CommandItem\n value=\"__create\"\n onSelect={() => {\n navigate({to: '/setup/workspaces/new' as never});\n onSelect?.();\n }}\n forceMount\n >\n <Icon name=\"addLine\" className=\"size-16\" />\n Create workspace\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n );\n}\n"],"names":["Command","CommandEmpty","CommandGroup","CommandInput","CommandItem","CommandList","CommandSeparator","Icon","useNavigate","useSetAtom","useAuthState","lastWorkspaceIdAtom","rememberLastWorkspaceId","WorkspaceSwitcher","activeWorkspaceId","onSelect","user","workspaces","navigate","setLastWorkspaceId","selectWorkspace","wid","workspace","find","candidate","id","to","params","workspaceSlug","slug","placeholder","className","div","heading","map","value","keywords","name","alwaysRender","forceMount"],"mappings":";AAAA,SACEA,OAAO,EACPC,YAAY,EACZC,YAAY,EACZC,YAAY,EACZC,WAAW,EACXC,WAAW,EACXC,gBAAgB,QACX,4BAA4B;AACnC,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,WAAW,QAAO,yBAAyB;AACnD,SAAQC,UAAU,QAAO,QAAQ;AACjC,SAAQC,YAAY,QAAO,mBAAmB;AAC9C,SAAQC,mBAAmB,EAAEC,uBAAuB,QAAO,6BAA6B;AAExF,OAAO,SAASC,kBAAkB,EAChCC,iBAAiB,EACjBC,QAAQ,EAIT;IACC,MAAM,EAACC,IAAI,EAAEC,UAAU,EAAC,GAAGP;IAC3B,MAAMQ,WAAWV;IACjB,MAAMW,qBAAqBV,WAAWE;IACtC,MAAMS,kBAAkB,CAACC;QACvB,MAAMC,YAAYL,WAAWM,IAAI,CAAC,CAACC,YAAcA,UAAUC,EAAE,KAAKJ;QAClE,IAAI,CAACC,WAAW;QAChB,IAAI;YACFH,mBAAmBE;YACnB,IAAIL,MAAMS,IAAIb,wBAAwBI,KAAKS,EAAE,EAAEJ;QACjD,EAAE,OAAM;QACN,gCAAgC;QAClC;QACAH,SAAS;YAACQ,IAAI;YAAqBC,QAAQ;gBAACC,eAAeN,UAAUO,IAAI;YAAA;QAAC;QAC1Ed;IACF;IACA,qBACE,MAACf;;0BACC,KAACG;gBAAa2B,aAAY;;0BAC1B,MAACzB;gBAAY0B,WAAU;;kCACrB,MAACC;wBAAID,WAAU;;0CACb,KAAC9B;0CAAa;;0CACd,KAACC;gCAAa+B,SAAQ;0CACnBhB,WAAWiB,GAAG,CAAC,CAACZ,0BACf,MAAClB;wCAEC+B,OAAOb,UAAUG,EAAE;wCACnBW,UAAU;4CAACd,UAAUe,IAAI;yCAAC;wCAC1BtB,UAAU,IAAMK,gBAAgBE,UAAUG,EAAE;;0DAE5C,KAAClB;gDACC8B,MAAK;gDACLN,WAAW,CAAC,aAAa,EAAEjB,sBAAsBQ,UAAUG,EAAE,GAAG,gBAAgB,aAAa;;4CAE9FH,UAAUe,IAAI;;uCATVf,UAAUG,EAAE;;;;kCAczB,KAACnB;wBAAiBgC,YAAY;wBAACP,WAAU;;kCACzC,KAAC7B;wBAAaqC,UAAU;wBAACR,WAAU;kCACjC,cAAA,MAAC3B;4BACC+B,OAAM;4BACNpB,UAAU;gCACRG,SAAS;oCAACQ,IAAI;gCAAgC;gCAC9CX;4BACF;4BACAwB,UAAU;;8CAEV,KAAChC;oCAAK8B,MAAK;oCAAUN,WAAU;;gCAAY;;;;;;;;AAOvD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose-routes.d.ts","sourceRoot":"","sources":["../../src/compose/compose-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,cAAc,CAAC;AAKtB,MAAM,WAAW,cAAe,SAAQ,kBAAkB;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AA0ID,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"compose-routes.d.ts","sourceRoot":"","sources":["../../src/compose/compose-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,cAAc,CAAC;AAKtB,MAAM,WAAW,cAAe,SAAQ,kBAAkB;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AA0ID,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,cAAc,EAAE,CAcnF;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,CAAC,EAAE,SAAS,cAAc,EAAE,GAC1C,aAAa,EAAE,CAuFjB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { anchorPaths } from '#runtime/anchor-paths.js';
|
|
1
|
+
import { anchorPaths, validateRoutePathInvariants } from '#runtime/anchor-paths.js';
|
|
2
2
|
import { LayoutCompositionError, RouteCompositionError } from './errors.js';
|
|
3
3
|
import { normalizeRoutePath } from './normalize-route-path.js';
|
|
4
4
|
function isShellAnchor(parent) {
|
|
@@ -102,6 +102,7 @@ export function composeLayouts(features) {
|
|
|
102
102
|
})));
|
|
103
103
|
const layoutById = validateLayoutParents(layouts);
|
|
104
104
|
for (const layout of layouts){
|
|
105
|
+
validateRoutePathInvariants(layout.path);
|
|
105
106
|
validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);
|
|
106
107
|
}
|
|
107
108
|
return layouts;
|
|
@@ -113,6 +114,7 @@ export function composeRoutes(features, providedLayouts) {
|
|
|
113
114
|
const layoutById = validateLayoutParents(layouts);
|
|
114
115
|
const layoutPaths = new Map();
|
|
115
116
|
for (const layout of layouts){
|
|
117
|
+
validateRoutePathInvariants(layout.path);
|
|
116
118
|
const existing = layoutPaths.get(layout.path);
|
|
117
119
|
if (existing) {
|
|
118
120
|
throw new RouteCompositionError(layout.path, `Route "${layout.path}" is contributed by both features "${existing.featureId}" and "${layout.featureId}".`, [
|
|
@@ -180,6 +182,7 @@ export function composeRoutes(features, providedLayouts) {
|
|
|
180
182
|
validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);
|
|
181
183
|
}
|
|
182
184
|
for (const route of routes.values()){
|
|
185
|
+
validateRoutePathInvariants(route.path);
|
|
183
186
|
validateNestedRoute(route.path, route.parent, layoutById, route.featureId);
|
|
184
187
|
}
|
|
185
188
|
return [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/compose/compose-routes.ts"],"sourcesContent":["import type {\n AnchorId,\n ClientFeature,\n LayoutContribution,\n RouteContribution,\n RouteParentId,\n} from '#contract.js';\nimport {anchorPaths} from '#runtime/anchor-paths.js';\nimport {LayoutCompositionError, RouteCompositionError} from './errors.js';\nimport {normalizeRoutePath} from './normalize-route-path.js';\n\nexport interface ComposedLayout extends LayoutContribution {\n featureId: string;\n}\n\nexport interface ComposedRoute extends RouteContribution {\n featureId: string;\n ownerFeatureId: string;\n}\n\nfunction isShellAnchor(parent: RouteParentId): parent is AnchorId {\n return Object.hasOwn(anchorPaths, parent);\n}\n\nfunction layoutParentPath(\n parent: RouteParentId,\n layouts: ReadonlyMap<string, ComposedLayout>,\n): string | undefined {\n if (isShellAnchor(parent)) return anchorPaths[parent];\n return layouts.get(parent)?.path;\n}\n\nfunction resolvedShellAnchor(\n parent: RouteParentId,\n layouts: ReadonlyMap<string, ComposedLayout>,\n): AnchorId | undefined {\n let current = parent;\n const visited = new Set<string>();\n while (!isShellAnchor(current)) {\n if (visited.has(current)) return undefined;\n visited.add(current);\n const next = layouts.get(current)?.parent;\n if (next === undefined) return undefined;\n current = next;\n }\n return current;\n}\n\nfunction validateNestedRoute(\n path: string,\n parent: RouteParentId,\n layouts: ReadonlyMap<string, ComposedLayout>,\n featureId: string,\n): void {\n if (resolvedShellAnchor(parent, layouts) === 'root') {\n const protectedAnchor = protectedAnchorForRootPath(path);\n if (protectedAnchor) {\n const [anchor, anchorPath] = protectedAnchor;\n throw new RouteCompositionError(\n path,\n `Route \"${path}\" in feature \"${featureId}\" cannot use root parent inside reserved anchor \"${anchor}\" (${anchorPath}). Use parent \"${anchor}\".`,\n [featureId],\n );\n }\n }\n const parentPath = layoutParentPath(parent, layouts);\n if (!parentPath) {\n if (isShellAnchor(parent)) return;\n throw new RouteCompositionError(\n path,\n `Route \"${path}\" in feature \"${featureId}\" targets missing layout parent \"${parent}\".`,\n [featureId],\n );\n }\n if (parentPath !== '/' && path !== parentPath && !path.startsWith(`${parentPath}/`)) {\n const parentKind = isShellAnchor(parent) ? 'anchor' : 'layout';\n throw new RouteCompositionError(\n path,\n `Route \"${path}\" must be nested under ${parentKind} \"${parent}\" (${parentPath}).`,\n [featureId],\n );\n }\n}\n\nconst anchorPathValues: ReadonlySet<string> = new Set(Object.values(anchorPaths));\nconst protectedAnchorPaths = Object.entries(anchorPaths)\n .filter(([anchor]) => anchor !== 'root')\n .sort(([, leftPath], [, rightPath]) => rightPath.length - leftPath.length) as Array<\n [Exclude<AnchorId, 'root'>, string]\n>;\n\nfunction protectedAnchorForRootPath(\n path: string,\n): readonly [Exclude<AnchorId, 'root'>, string] | undefined {\n return protectedAnchorPaths.find(\n ([, anchorPath]) => path === anchorPath || path.startsWith(`${anchorPath}/`),\n );\n}\n\nfunction validateLayoutParents(layouts: readonly ComposedLayout[]): Map<string, ComposedLayout> {\n const byId = new Map<string, ComposedLayout>();\n for (const layout of layouts) {\n if (isShellAnchor(layout.id)) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout id \"${layout.id}\" in feature \"${layout.featureId}\" is reserved by the shell.`,\n [layout.featureId],\n );\n }\n if (anchorPathValues.has(layout.path)) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout \"${layout.id}\" in feature \"${layout.featureId}\" targets path \"${layout.path}\" which is reserved by a shell anchor.`,\n [layout.featureId],\n );\n }\n const existing = byId.get(layout.id);\n if (existing) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout id \"${layout.id}\" is contributed by both features \"${existing.featureId}\" and \"${layout.featureId}\".`,\n [existing.featureId, layout.featureId],\n );\n }\n byId.set(layout.id, layout);\n }\n\n const visiting = new Set<string>();\n const visited = new Set<string>();\n function visit(layout: ComposedLayout): void {\n if (visited.has(layout.id) || isShellAnchor(layout.parent)) return;\n if (visiting.has(layout.id)) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout \"${layout.id}\" in feature \"${layout.featureId}\" has a cyclic parent reference.`,\n [layout.featureId],\n );\n }\n const parent = byId.get(layout.parent);\n if (!parent) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout \"${layout.id}\" in feature \"${layout.featureId}\" targets missing layout parent \"${layout.parent}\".`,\n [layout.featureId],\n );\n }\n visiting.add(layout.id);\n visit(parent);\n visiting.delete(layout.id);\n visited.add(layout.id);\n }\n\n for (const layout of layouts) visit(layout);\n return byId;\n}\n\nexport function composeLayouts(features: readonly ClientFeature[]): ComposedLayout[] {\n const layouts = features.flatMap((feature) =>\n (feature.layouts ?? []).map((layout) => ({\n ...layout,\n path: normalizeRoutePath(layout.path),\n featureId: feature.id,\n })),\n );\n const layoutById = validateLayoutParents(layouts);\n for (const layout of layouts) {\n validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);\n }\n return layouts;\n}\n\nexport function composeRoutes(\n features: readonly ClientFeature[],\n providedLayouts?: readonly ComposedLayout[],\n): ComposedRoute[] {\n const layouts = providedLayouts ? [...providedLayouts] : composeLayouts(features);\n const layoutById = validateLayoutParents(layouts);\n const layoutPaths = new Map<string, ComposedLayout>();\n for (const layout of layouts) {\n const existing = layoutPaths.get(layout.path);\n if (existing) {\n throw new RouteCompositionError(\n layout.path,\n `Route \"${layout.path}\" is contributed by both features \"${existing.featureId}\" and \"${layout.featureId}\".`,\n [existing.featureId, layout.featureId],\n );\n }\n layoutPaths.set(layout.path, layout);\n }\n\n const routes = new Map<string, ComposedRoute>();\n for (const feature of features) {\n for (const contribution of feature.routes ?? []) {\n const normalizedContribution = {\n ...contribution,\n path: normalizeRoutePath(contribution.path),\n };\n const layoutAtPath = layoutPaths.get(normalizedContribution.path);\n if (layoutAtPath) {\n const message = normalizedContribution.override\n ? `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" cannot replace layout \"${layoutAtPath.id}\" contributed by feature \"${layoutAtPath.featureId}\".`\n : `Route \"${normalizedContribution.path}\" from feature \"${feature.id}\" conflicts with layout \"${layoutAtPath.id}\" contributed by feature \"${layoutAtPath.featureId}\". Routes cannot replace layouts.`;\n throw new RouteCompositionError(normalizedContribution.path, message, [\n layoutAtPath.featureId,\n feature.id,\n ]);\n }\n const existing = routes.get(normalizedContribution.path);\n if (!existing) {\n if (normalizedContribution.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" has no route to replace.`,\n [feature.id],\n );\n }\n routes.set(normalizedContribution.path, {\n ...normalizedContribution,\n featureId: feature.id,\n ownerFeatureId: feature.id,\n });\n continue;\n }\n if (!normalizedContribution.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route \"${normalizedContribution.path}\" is contributed by both features \"${existing.featureId}\" and \"${feature.id}\". Set override: true to replace it explicitly.`,\n [existing.featureId, feature.id],\n );\n }\n if (existing.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route \"${normalizedContribution.path}\" has competing overrides from features \"${existing.featureId}\" and \"${feature.id}\".`,\n [existing.featureId, feature.id],\n );\n }\n if (existing.parent !== normalizedContribution.parent) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" cannot change anchor from \"${existing.parent}\" in feature \"${existing.featureId}\" to \"${normalizedContribution.parent}\".`,\n [existing.featureId, feature.id],\n );\n }\n routes.set(normalizedContribution.path, {\n ...normalizedContribution,\n featureId: feature.id,\n ownerFeatureId: existing.ownerFeatureId,\n });\n }\n }\n\n for (const layout of layouts) {\n validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);\n }\n for (const route of routes.values()) {\n validateNestedRoute(route.path, route.parent, layoutById, route.featureId);\n }\n return [...routes.values()];\n}\n"],"names":["anchorPaths","LayoutCompositionError","RouteCompositionError","normalizeRoutePath","isShellAnchor","parent","Object","hasOwn","layoutParentPath","layouts","get","path","resolvedShellAnchor","current","visited","Set","has","undefined","add","next","validateNestedRoute","featureId","protectedAnchor","protectedAnchorForRootPath","anchor","anchorPath","parentPath","startsWith","parentKind","anchorPathValues","values","protectedAnchorPaths","entries","filter","sort","leftPath","rightPath","length","find","validateLayoutParents","byId","Map","layout","id","existing","set","visiting","visit","delete","composeLayouts","features","flatMap","feature","map","layoutById","composeRoutes","providedLayouts","layoutPaths","routes","contribution","normalizedContribution","layoutAtPath","message","override","ownerFeatureId","route"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/compose/compose-routes.ts"],"sourcesContent":["import type {\n AnchorId,\n ClientFeature,\n LayoutContribution,\n RouteContribution,\n RouteParentId,\n} from '#contract.js';\nimport {anchorPaths, validateRoutePathInvariants} from '#runtime/anchor-paths.js';\nimport {LayoutCompositionError, RouteCompositionError} from './errors.js';\nimport {normalizeRoutePath} from './normalize-route-path.js';\n\nexport interface ComposedLayout extends LayoutContribution {\n featureId: string;\n}\n\nexport interface ComposedRoute extends RouteContribution {\n featureId: string;\n ownerFeatureId: string;\n}\n\nfunction isShellAnchor(parent: RouteParentId): parent is AnchorId {\n return Object.hasOwn(anchorPaths, parent);\n}\n\nfunction layoutParentPath(\n parent: RouteParentId,\n layouts: ReadonlyMap<string, ComposedLayout>,\n): string | undefined {\n if (isShellAnchor(parent)) return anchorPaths[parent];\n return layouts.get(parent)?.path;\n}\n\nfunction resolvedShellAnchor(\n parent: RouteParentId,\n layouts: ReadonlyMap<string, ComposedLayout>,\n): AnchorId | undefined {\n let current = parent;\n const visited = new Set<string>();\n while (!isShellAnchor(current)) {\n if (visited.has(current)) return undefined;\n visited.add(current);\n const next = layouts.get(current)?.parent;\n if (next === undefined) return undefined;\n current = next;\n }\n return current;\n}\n\nfunction validateNestedRoute(\n path: string,\n parent: RouteParentId,\n layouts: ReadonlyMap<string, ComposedLayout>,\n featureId: string,\n): void {\n if (resolvedShellAnchor(parent, layouts) === 'root') {\n const protectedAnchor = protectedAnchorForRootPath(path);\n if (protectedAnchor) {\n const [anchor, anchorPath] = protectedAnchor;\n throw new RouteCompositionError(\n path,\n `Route \"${path}\" in feature \"${featureId}\" cannot use root parent inside reserved anchor \"${anchor}\" (${anchorPath}). Use parent \"${anchor}\".`,\n [featureId],\n );\n }\n }\n const parentPath = layoutParentPath(parent, layouts);\n if (!parentPath) {\n if (isShellAnchor(parent)) return;\n throw new RouteCompositionError(\n path,\n `Route \"${path}\" in feature \"${featureId}\" targets missing layout parent \"${parent}\".`,\n [featureId],\n );\n }\n if (parentPath !== '/' && path !== parentPath && !path.startsWith(`${parentPath}/`)) {\n const parentKind = isShellAnchor(parent) ? 'anchor' : 'layout';\n throw new RouteCompositionError(\n path,\n `Route \"${path}\" must be nested under ${parentKind} \"${parent}\" (${parentPath}).`,\n [featureId],\n );\n }\n}\n\nconst anchorPathValues: ReadonlySet<string> = new Set(Object.values(anchorPaths));\nconst protectedAnchorPaths = Object.entries(anchorPaths)\n .filter(([anchor]) => anchor !== 'root')\n .sort(([, leftPath], [, rightPath]) => rightPath.length - leftPath.length) as Array<\n [Exclude<AnchorId, 'root'>, string]\n>;\n\nfunction protectedAnchorForRootPath(\n path: string,\n): readonly [Exclude<AnchorId, 'root'>, string] | undefined {\n return protectedAnchorPaths.find(\n ([, anchorPath]) => path === anchorPath || path.startsWith(`${anchorPath}/`),\n );\n}\n\nfunction validateLayoutParents(layouts: readonly ComposedLayout[]): Map<string, ComposedLayout> {\n const byId = new Map<string, ComposedLayout>();\n for (const layout of layouts) {\n if (isShellAnchor(layout.id)) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout id \"${layout.id}\" in feature \"${layout.featureId}\" is reserved by the shell.`,\n [layout.featureId],\n );\n }\n if (anchorPathValues.has(layout.path)) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout \"${layout.id}\" in feature \"${layout.featureId}\" targets path \"${layout.path}\" which is reserved by a shell anchor.`,\n [layout.featureId],\n );\n }\n const existing = byId.get(layout.id);\n if (existing) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout id \"${layout.id}\" is contributed by both features \"${existing.featureId}\" and \"${layout.featureId}\".`,\n [existing.featureId, layout.featureId],\n );\n }\n byId.set(layout.id, layout);\n }\n\n const visiting = new Set<string>();\n const visited = new Set<string>();\n function visit(layout: ComposedLayout): void {\n if (visited.has(layout.id) || isShellAnchor(layout.parent)) return;\n if (visiting.has(layout.id)) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout \"${layout.id}\" in feature \"${layout.featureId}\" has a cyclic parent reference.`,\n [layout.featureId],\n );\n }\n const parent = byId.get(layout.parent);\n if (!parent) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout \"${layout.id}\" in feature \"${layout.featureId}\" targets missing layout parent \"${layout.parent}\".`,\n [layout.featureId],\n );\n }\n visiting.add(layout.id);\n visit(parent);\n visiting.delete(layout.id);\n visited.add(layout.id);\n }\n\n for (const layout of layouts) visit(layout);\n return byId;\n}\n\nexport function composeLayouts(features: readonly ClientFeature[]): ComposedLayout[] {\n const layouts = features.flatMap((feature) =>\n (feature.layouts ?? []).map((layout) => ({\n ...layout,\n path: normalizeRoutePath(layout.path),\n featureId: feature.id,\n })),\n );\n const layoutById = validateLayoutParents(layouts);\n for (const layout of layouts) {\n validateRoutePathInvariants(layout.path);\n validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);\n }\n return layouts;\n}\n\nexport function composeRoutes(\n features: readonly ClientFeature[],\n providedLayouts?: readonly ComposedLayout[],\n): ComposedRoute[] {\n const layouts = providedLayouts ? [...providedLayouts] : composeLayouts(features);\n const layoutById = validateLayoutParents(layouts);\n const layoutPaths = new Map<string, ComposedLayout>();\n for (const layout of layouts) {\n validateRoutePathInvariants(layout.path);\n const existing = layoutPaths.get(layout.path);\n if (existing) {\n throw new RouteCompositionError(\n layout.path,\n `Route \"${layout.path}\" is contributed by both features \"${existing.featureId}\" and \"${layout.featureId}\".`,\n [existing.featureId, layout.featureId],\n );\n }\n layoutPaths.set(layout.path, layout);\n }\n\n const routes = new Map<string, ComposedRoute>();\n for (const feature of features) {\n for (const contribution of feature.routes ?? []) {\n const normalizedContribution = {\n ...contribution,\n path: normalizeRoutePath(contribution.path),\n };\n const layoutAtPath = layoutPaths.get(normalizedContribution.path);\n if (layoutAtPath) {\n const message = normalizedContribution.override\n ? `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" cannot replace layout \"${layoutAtPath.id}\" contributed by feature \"${layoutAtPath.featureId}\".`\n : `Route \"${normalizedContribution.path}\" from feature \"${feature.id}\" conflicts with layout \"${layoutAtPath.id}\" contributed by feature \"${layoutAtPath.featureId}\". Routes cannot replace layouts.`;\n throw new RouteCompositionError(normalizedContribution.path, message, [\n layoutAtPath.featureId,\n feature.id,\n ]);\n }\n const existing = routes.get(normalizedContribution.path);\n if (!existing) {\n if (normalizedContribution.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" has no route to replace.`,\n [feature.id],\n );\n }\n routes.set(normalizedContribution.path, {\n ...normalizedContribution,\n featureId: feature.id,\n ownerFeatureId: feature.id,\n });\n continue;\n }\n if (!normalizedContribution.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route \"${normalizedContribution.path}\" is contributed by both features \"${existing.featureId}\" and \"${feature.id}\". Set override: true to replace it explicitly.`,\n [existing.featureId, feature.id],\n );\n }\n if (existing.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route \"${normalizedContribution.path}\" has competing overrides from features \"${existing.featureId}\" and \"${feature.id}\".`,\n [existing.featureId, feature.id],\n );\n }\n if (existing.parent !== normalizedContribution.parent) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" cannot change anchor from \"${existing.parent}\" in feature \"${existing.featureId}\" to \"${normalizedContribution.parent}\".`,\n [existing.featureId, feature.id],\n );\n }\n routes.set(normalizedContribution.path, {\n ...normalizedContribution,\n featureId: feature.id,\n ownerFeatureId: existing.ownerFeatureId,\n });\n }\n }\n\n for (const layout of layouts) {\n validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);\n }\n for (const route of routes.values()) {\n validateRoutePathInvariants(route.path);\n validateNestedRoute(route.path, route.parent, layoutById, route.featureId);\n }\n return [...routes.values()];\n}\n"],"names":["anchorPaths","validateRoutePathInvariants","LayoutCompositionError","RouteCompositionError","normalizeRoutePath","isShellAnchor","parent","Object","hasOwn","layoutParentPath","layouts","get","path","resolvedShellAnchor","current","visited","Set","has","undefined","add","next","validateNestedRoute","featureId","protectedAnchor","protectedAnchorForRootPath","anchor","anchorPath","parentPath","startsWith","parentKind","anchorPathValues","values","protectedAnchorPaths","entries","filter","sort","leftPath","rightPath","length","find","validateLayoutParents","byId","Map","layout","id","existing","set","visiting","visit","delete","composeLayouts","features","flatMap","feature","map","layoutById","composeRoutes","providedLayouts","layoutPaths","routes","contribution","normalizedContribution","layoutAtPath","message","override","ownerFeatureId","route"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-registries.d.ts","sourceRoot":"","sources":["../../src/compose/validate-registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAe,aAAa,EAAC,MAAM,cAAc,CAAC;AAI5E,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,KAAK,eAAe,GAAG,QAAQ,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;AAEzD,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AA0CD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,EAAE,eAAe,EAChC,gBAAgB,GAAE,SAAS,eAAe,EAAO,GAChD,IAAI,CAuEN;AA4BD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,EAAE,eAAe,GAC/B,IAAI,
|
|
1
|
+
{"version":3,"file":"validate-registries.d.ts","sourceRoot":"","sources":["../../src/compose/validate-registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAe,aAAa,EAAC,MAAM,cAAc,CAAC;AAI5E,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,KAAK,eAAe,GAAG,QAAQ,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;AAEzD,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AA0CD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,EAAE,eAAe,EAChC,gBAAgB,GAAE,SAAS,eAAe,EAAO,GAChD,IAAI,CAuEN;AA4BD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,EAAE,eAAe,GAC/B,IAAI,CAoCN"}
|
|
@@ -133,7 +133,7 @@ export function validateSettingsSections(features, routeReferences) {
|
|
|
133
133
|
feature.id
|
|
134
134
|
]);
|
|
135
135
|
}
|
|
136
|
-
const path = `/
|
|
136
|
+
const path = section.scope === 'project' ? `/w/$workspaceSlug/p/$projectSlug/settings/${section.pathSegment}` : `/w/$workspaceSlug/settings/${section.pathSegment}`;
|
|
137
137
|
const normalizedPath = normalizeRoutePath(path);
|
|
138
138
|
const routeOwner = routes.get(normalizedPath);
|
|
139
139
|
if (routeOwner === undefined && !routes.has(normalizedPath)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/compose/validate-registries.ts"],"sourcesContent":["import type {ClientFeature, NavTabEntry, RouteParentId} from '#contract.js';\nimport {NavCompositionError, SettingsCompositionError} from './errors.js';\nimport {normalizeRoutePath} from './normalize-route-path.js';\n\ninterface RouteReference {\n path: string;\n featureId?: string;\n ownerFeatureId?: string;\n parent?: RouteParentId;\n}\n\ntype RouteReferences = Iterable<string | RouteReference>;\n\ninterface LayoutReference {\n id: string;\n path: string;\n featureId?: string;\n parent?: RouteParentId;\n}\n\nfunction routeOwners(routeReferences: RouteReferences): Map<string, string | undefined> {\n const owners = new Map<string, string | undefined>();\n for (const route of routeReferences) {\n const path = typeof route === 'string' ? route : route.path;\n owners.set(\n normalizeRoutePath(path),\n typeof route === 'string' ? undefined : (route.ownerFeatureId ?? route.featureId),\n );\n }\n return owners;\n}\n\nfunction hasExplicitCoordinator(feature: ClientFeature): boolean {\n return feature.coordinator === feature.id;\n}\n\nfunction validateRoleMetadata(entry: NavTabEntry, feature: ClientFeature): void {\n const rawEntry = entry as NavTabEntry & {minimumRole?: unknown};\n const hasMinimumRole = Object.hasOwn(rawEntry, 'minimumRole');\n if (entry.scope !== 'layout') {\n if (hasMinimumRole) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" has minimum role metadata but is not layout-scoped.`,\n [feature.id],\n );\n }\n return;\n }\n if (hasMinimumRole) {\n if (typeof rawEntry.minimumRole !== 'string' || rawEntry.minimumRole.trim() === '') {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" has invalid minimum role metadata. Expected a non-empty string.`,\n [feature.id],\n );\n }\n }\n}\n\nexport function validateNavigation(\n features: readonly ClientFeature[],\n routeReferences: RouteReferences,\n layoutReferences: readonly LayoutReference[] = [],\n): void {\n const routeReferencesWithLayouts: Array<string | RouteReference> = [...routeReferences];\n for (const layout of layoutReferences) {\n routeReferencesWithLayouts.push({\n path: layout.path,\n ...(layout.featureId ? {featureId: layout.featureId, ownerFeatureId: layout.featureId} : {}),\n ...(layout.parent ? {parent: layout.parent} : {}),\n });\n }\n const routes = routeOwners(routeReferencesWithLayouts);\n const layouts = new Map(layoutReferences.map((layout) => [layout.id, layout]));\n const entries = new Map<string, string>();\n for (const feature of features) {\n for (const entry of feature.navigation ?? []) {\n validateRoleMetadata(entry, feature);\n const existingFeatureId = entries.get(entry.id);\n if (existingFeatureId) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" is contributed by both features \"${existingFeatureId}\" and \"${feature.id}\".`,\n [existingFeatureId, feature.id],\n );\n }\n const target = normalizeRoutePath(entry.to);\n const routeOwner = routes.get(target);\n if (routeOwner === undefined && !routes.has(target)) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets missing route \"${target}\".`,\n [feature.id],\n );\n }\n if (entry.scope === 'layout') {\n if (typeof entry.layout !== 'string' || entry.layout.trim() === '') {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" has invalid layout metadata. Expected a non-empty layout id.`,\n [feature.id],\n );\n }\n const layout = layouts.get(entry.layout);\n if (!layout) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets missing layout \"${entry.layout}\".`,\n [feature.id],\n );\n }\n const route = routeReferencesForPath(routeReferencesWithLayouts, target);\n const isLayoutRoot = route?.path === layout.path;\n const isLayoutDescendant = routeDescendsFromLayout(route, entry.layout, layouts);\n if (!isLayoutRoot && !isLayoutDescendant) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets route \"${target}\" outside layout \"${entry.layout}\".`,\n [feature.id],\n );\n }\n entries.set(entry.id, feature.id);\n continue;\n }\n if (routeOwner && routeOwner !== feature.id && !hasExplicitCoordinator(feature)) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets route \"${target}\" owned by feature \"${routeOwner}\". Declare coordinator: \"${feature.id}\" to own this cross-feature contribution.`,\n [routeOwner, feature.id],\n );\n }\n entries.set(entry.id, feature.id);\n }\n }\n}\n\nfunction routeReferencesForPath(\n routeReferences: RouteReferences,\n path: string,\n): RouteReference | undefined {\n for (const route of routeReferences) {\n const reference = typeof route === 'string' ? {path: route} : route;\n if (normalizeRoutePath(reference.path) === path) return reference;\n }\n return undefined;\n}\n\nfunction routeDescendsFromLayout(\n route: RouteReference | undefined,\n layoutId: string,\n layouts: ReadonlyMap<string, LayoutReference>,\n): boolean {\n const visited = new Set<string>();\n let parent = route?.parent;\n while (parent && !visited.has(parent)) {\n if (parent === layoutId) return true;\n visited.add(parent);\n parent = layouts.get(parent)?.parent;\n }\n return false;\n}\n\nexport function validateSettingsSections(\n features: readonly ClientFeature[],\n routeReferences: RouteReferences,\n): void {\n const routes = routeOwners(routeReferences);\n const sections = new Map<string, string>();\n for (const feature of features) {\n for (const section of feature.settingsSections ?? []) {\n const existingFeatureId = sections.get(section.id);\n if (existingFeatureId) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" is contributed by both features \"${existingFeatureId}\" and \"${feature.id}\".`,\n [existingFeatureId, feature.id],\n );\n }\n const path = `/workspaces/$wid/settings/${section.pathSegment}`;\n const normalizedPath = normalizeRoutePath(path);\n const routeOwner = routes.get(normalizedPath);\n if (routeOwner === undefined && !routes.has(normalizedPath)) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" in feature \"${feature.id}\" requires route \"${path}\".`,\n [feature.id],\n );\n }\n if (routeOwner && routeOwner !== feature.id && !hasExplicitCoordinator(feature)) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" in feature \"${feature.id}\" targets route \"${path}\" owned by feature \"${routeOwner}\". Declare coordinator: \"${feature.id}\" to own this cross-feature contribution.`,\n [routeOwner, feature.id],\n );\n }\n sections.set(section.id, feature.id);\n }\n }\n}\n"],"names":["NavCompositionError","SettingsCompositionError","normalizeRoutePath","routeOwners","routeReferences","owners","Map","route","path","set","undefined","ownerFeatureId","featureId","hasExplicitCoordinator","feature","coordinator","id","validateRoleMetadata","entry","rawEntry","hasMinimumRole","Object","hasOwn","scope","minimumRole","trim","validateNavigation","features","layoutReferences","routeReferencesWithLayouts","layout","push","parent","routes","layouts","map","entries","navigation","existingFeatureId","get","target","to","routeOwner","has","routeReferencesForPath","isLayoutRoot","isLayoutDescendant","routeDescendsFromLayout","reference","layoutId","visited","Set","add","validateSettingsSections","sections","section","settingsSections","pathSegment","normalizedPath"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/compose/validate-registries.ts"],"sourcesContent":["import type {ClientFeature, NavTabEntry, RouteParentId} from '#contract.js';\nimport {NavCompositionError, SettingsCompositionError} from './errors.js';\nimport {normalizeRoutePath} from './normalize-route-path.js';\n\ninterface RouteReference {\n path: string;\n featureId?: string;\n ownerFeatureId?: string;\n parent?: RouteParentId;\n}\n\ntype RouteReferences = Iterable<string | RouteReference>;\n\ninterface LayoutReference {\n id: string;\n path: string;\n featureId?: string;\n parent?: RouteParentId;\n}\n\nfunction routeOwners(routeReferences: RouteReferences): Map<string, string | undefined> {\n const owners = new Map<string, string | undefined>();\n for (const route of routeReferences) {\n const path = typeof route === 'string' ? route : route.path;\n owners.set(\n normalizeRoutePath(path),\n typeof route === 'string' ? undefined : (route.ownerFeatureId ?? route.featureId),\n );\n }\n return owners;\n}\n\nfunction hasExplicitCoordinator(feature: ClientFeature): boolean {\n return feature.coordinator === feature.id;\n}\n\nfunction validateRoleMetadata(entry: NavTabEntry, feature: ClientFeature): void {\n const rawEntry = entry as NavTabEntry & {minimumRole?: unknown};\n const hasMinimumRole = Object.hasOwn(rawEntry, 'minimumRole');\n if (entry.scope !== 'layout') {\n if (hasMinimumRole) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" has minimum role metadata but is not layout-scoped.`,\n [feature.id],\n );\n }\n return;\n }\n if (hasMinimumRole) {\n if (typeof rawEntry.minimumRole !== 'string' || rawEntry.minimumRole.trim() === '') {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" has invalid minimum role metadata. Expected a non-empty string.`,\n [feature.id],\n );\n }\n }\n}\n\nexport function validateNavigation(\n features: readonly ClientFeature[],\n routeReferences: RouteReferences,\n layoutReferences: readonly LayoutReference[] = [],\n): void {\n const routeReferencesWithLayouts: Array<string | RouteReference> = [...routeReferences];\n for (const layout of layoutReferences) {\n routeReferencesWithLayouts.push({\n path: layout.path,\n ...(layout.featureId ? {featureId: layout.featureId, ownerFeatureId: layout.featureId} : {}),\n ...(layout.parent ? {parent: layout.parent} : {}),\n });\n }\n const routes = routeOwners(routeReferencesWithLayouts);\n const layouts = new Map(layoutReferences.map((layout) => [layout.id, layout]));\n const entries = new Map<string, string>();\n for (const feature of features) {\n for (const entry of feature.navigation ?? []) {\n validateRoleMetadata(entry, feature);\n const existingFeatureId = entries.get(entry.id);\n if (existingFeatureId) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" is contributed by both features \"${existingFeatureId}\" and \"${feature.id}\".`,\n [existingFeatureId, feature.id],\n );\n }\n const target = normalizeRoutePath(entry.to);\n const routeOwner = routes.get(target);\n if (routeOwner === undefined && !routes.has(target)) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets missing route \"${target}\".`,\n [feature.id],\n );\n }\n if (entry.scope === 'layout') {\n if (typeof entry.layout !== 'string' || entry.layout.trim() === '') {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" has invalid layout metadata. Expected a non-empty layout id.`,\n [feature.id],\n );\n }\n const layout = layouts.get(entry.layout);\n if (!layout) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets missing layout \"${entry.layout}\".`,\n [feature.id],\n );\n }\n const route = routeReferencesForPath(routeReferencesWithLayouts, target);\n const isLayoutRoot = route?.path === layout.path;\n const isLayoutDescendant = routeDescendsFromLayout(route, entry.layout, layouts);\n if (!isLayoutRoot && !isLayoutDescendant) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets route \"${target}\" outside layout \"${entry.layout}\".`,\n [feature.id],\n );\n }\n entries.set(entry.id, feature.id);\n continue;\n }\n if (routeOwner && routeOwner !== feature.id && !hasExplicitCoordinator(feature)) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets route \"${target}\" owned by feature \"${routeOwner}\". Declare coordinator: \"${feature.id}\" to own this cross-feature contribution.`,\n [routeOwner, feature.id],\n );\n }\n entries.set(entry.id, feature.id);\n }\n }\n}\n\nfunction routeReferencesForPath(\n routeReferences: RouteReferences,\n path: string,\n): RouteReference | undefined {\n for (const route of routeReferences) {\n const reference = typeof route === 'string' ? {path: route} : route;\n if (normalizeRoutePath(reference.path) === path) return reference;\n }\n return undefined;\n}\n\nfunction routeDescendsFromLayout(\n route: RouteReference | undefined,\n layoutId: string,\n layouts: ReadonlyMap<string, LayoutReference>,\n): boolean {\n const visited = new Set<string>();\n let parent = route?.parent;\n while (parent && !visited.has(parent)) {\n if (parent === layoutId) return true;\n visited.add(parent);\n parent = layouts.get(parent)?.parent;\n }\n return false;\n}\n\nexport function validateSettingsSections(\n features: readonly ClientFeature[],\n routeReferences: RouteReferences,\n): void {\n const routes = routeOwners(routeReferences);\n const sections = new Map<string, string>();\n for (const feature of features) {\n for (const section of feature.settingsSections ?? []) {\n const existingFeatureId = sections.get(section.id);\n if (existingFeatureId) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" is contributed by both features \"${existingFeatureId}\" and \"${feature.id}\".`,\n [existingFeatureId, feature.id],\n );\n }\n const path =\n section.scope === 'project'\n ? `/w/$workspaceSlug/p/$projectSlug/settings/${section.pathSegment}`\n : `/w/$workspaceSlug/settings/${section.pathSegment}`;\n const normalizedPath = normalizeRoutePath(path);\n const routeOwner = routes.get(normalizedPath);\n if (routeOwner === undefined && !routes.has(normalizedPath)) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" in feature \"${feature.id}\" requires route \"${path}\".`,\n [feature.id],\n );\n }\n if (routeOwner && routeOwner !== feature.id && !hasExplicitCoordinator(feature)) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" in feature \"${feature.id}\" targets route \"${path}\" owned by feature \"${routeOwner}\". Declare coordinator: \"${feature.id}\" to own this cross-feature contribution.`,\n [routeOwner, feature.id],\n );\n }\n sections.set(section.id, feature.id);\n }\n }\n}\n"],"names":["NavCompositionError","SettingsCompositionError","normalizeRoutePath","routeOwners","routeReferences","owners","Map","route","path","set","undefined","ownerFeatureId","featureId","hasExplicitCoordinator","feature","coordinator","id","validateRoleMetadata","entry","rawEntry","hasMinimumRole","Object","hasOwn","scope","minimumRole","trim","validateNavigation","features","layoutReferences","routeReferencesWithLayouts","layout","push","parent","routes","layouts","map","entries","navigation","existingFeatureId","get","target","to","routeOwner","has","routeReferencesForPath","isLayoutRoot","isLayoutDescendant","routeDescendsFromLayout","reference","layoutId","visited","Set","add","validateSettingsSections","sections","section","settingsSections","pathSegment","normalizedPath"],"mappings":"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"}
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IconName } from '@shipfox/react-ui/icon';
|
|
2
2
|
import type { ComponentType, PropsWithChildren } from 'react';
|
|
3
3
|
import type { z } from 'zod';
|
|
4
|
-
export type AnchorId = 'root' | 'workspaceLayout' | 'projectLayout' | 'workspaceSettings';
|
|
4
|
+
export type AnchorId = 'root' | 'workspaceLayout' | 'projectLayout' | 'workspaceSettings' | 'projectSettings';
|
|
5
5
|
/** A fixed shell anchor or the stable id of a feature-owned layout. */
|
|
6
6
|
export type RouteParentId = AnchorId | (string & {});
|
|
7
7
|
export interface RouteContribution {
|
|
@@ -43,6 +43,7 @@ export interface SettingsSectionEntry {
|
|
|
43
43
|
pathSegment: string;
|
|
44
44
|
label: string;
|
|
45
45
|
icon: IconName;
|
|
46
|
+
scope?: 'workspace' | 'project';
|
|
46
47
|
order?: number;
|
|
47
48
|
}
|
|
48
49
|
export interface ClientFeature<S extends z.ZodRawShape = z.ZodRawShape> {
|
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAC,aAAa,EAAE,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,MAAM,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAC,aAAa,EAAE,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,MAAM,MAAM,QAAQ,GAChB,MAAM,GACN,iBAAiB,GACjB,eAAe,GACf,mBAAmB,GACnB,iBAAiB,CAAC;AAEtB,uEAAuE;AACvE,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;CAC7C;AAED,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,WAAW,GACnB,CAAC,eAAe,GAAG;IAAC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAC,CAAC,GACpE,CAAC,eAAe,GAAG;IAAC,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAC,CAAC,CAAC;AAEhF,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,WAAW,EAAE;IAAC,KAAK,EAAE,QAAQ,CAAA;CAAC,CAAC,CAAC;AAE5E,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW;IACpE,EAAE,EAAE,MAAM,CAAC;IACX;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACxC,MAAM,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IACpC,gBAAgB,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACnD,WAAW,CAAC,EAAE,CAAC,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,CAAC,SAAS,aAAa,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAEhF"}
|
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/contract.ts"],"sourcesContent":["import type {IconName} from '@shipfox/react-ui/icon';\nimport type {ComponentType, PropsWithChildren} from 'react';\nimport type {z} from 'zod';\n\nexport type AnchorId
|
|
1
|
+
{"version":3,"sources":["../src/contract.ts"],"sourcesContent":["import type {IconName} from '@shipfox/react-ui/icon';\nimport type {ComponentType, PropsWithChildren} from 'react';\nimport type {z} from 'zod';\n\nexport type AnchorId =\n | 'root'\n | 'workspaceLayout'\n | 'projectLayout'\n | 'workspaceSettings'\n | 'projectSettings';\n\n/** A fixed shell anchor or the stable id of a feature-owned layout. */\nexport type RouteParentId = AnchorId | (string & {});\n\nexport interface RouteContribution {\n path: string;\n parent: RouteParentId;\n override?: boolean;\n impl: string;\n}\n\nexport interface LayoutContribution {\n id: string;\n path: string;\n parent: RouteParentId;\n impl: string;\n}\n\nexport interface FeatureProvider {\n id: string;\n Component: ComponentType<PropsWithChildren>;\n}\n\ninterface NavTabEntryBase {\n id: string;\n label: string;\n to: string;\n exact?: boolean;\n order?: number;\n}\n\nexport type NavTabEntry =\n | (NavTabEntryBase & {scope: 'workspace' | 'project'; layout?: never})\n | (NavTabEntryBase & {scope: 'layout'; layout: string; minimumRole?: string});\n\nexport type LayoutNavigationEntry = Extract<NavTabEntry, {scope: 'layout'}>;\n\nexport interface SettingsSectionEntry {\n id: string;\n pathSegment: string;\n label: string;\n icon: IconName;\n scope?: 'workspace' | 'project';\n order?: number;\n}\n\nexport interface ClientFeature<S extends z.ZodRawShape = z.ZodRawShape> {\n id: string;\n /**\n * Set this to the feature id when the feature intentionally coordinates a\n * navigation or settings contribution whose route belongs to another\n * feature.\n */\n coordinator?: string;\n layouts?: readonly LayoutContribution[];\n routes?: readonly RouteContribution[];\n providers?: readonly FeatureProvider[];\n navigation?: readonly NavTabEntry[];\n settingsSections?: readonly SettingsSectionEntry[];\n configShape?: S;\n}\n\nexport function defineClientFeature<const T extends ClientFeature>(feature: T): T {\n return feature;\n}\n"],"names":["defineClientFeature","feature"],"mappings":"AAwEA,OAAO,SAASA,oBAAmDC,OAAU;IAC3E,OAAOA;AACT"}
|
package/dist/core/session.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,aAAa,CAAC;AAE5E,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;CAC7C"}
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,aAAa,CAAC;AAE5E,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;CAC7C"}
|
package/dist/core/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/session.ts"],"sourcesContent":["export type AdminRole = 'admin-observer' | 'admin-operator' | 'admin-owner';\n\nexport interface UserIdentity {\n id: string;\n email: string;\n name?: string;\n emailVerifiedAt?: string;\n adminRole?: AdminRole;\n}\n\nexport interface AuthenticatedSession {\n accessToken: string;\n user: UserIdentity;\n}\n\nexport interface WorkspaceMembership {\n id: string;\n workspaceId: string;\n}\n\nexport interface WorkspaceSummary {\n id: string;\n name: string;\n membershipId: string;\n status?: 'active' | 'suspended' | 'deleted';\n}\n"],"names":[],"mappings":"AAoBA,
|
|
1
|
+
{"version":3,"sources":["../../src/core/session.ts"],"sourcesContent":["export type AdminRole = 'admin-observer' | 'admin-operator' | 'admin-owner';\n\nexport interface UserIdentity {\n id: string;\n email: string;\n name?: string;\n emailVerifiedAt?: string;\n adminRole?: AdminRole;\n}\n\nexport interface AuthenticatedSession {\n accessToken: string;\n user: UserIdentity;\n}\n\nexport interface WorkspaceMembership {\n id: string;\n workspaceId: string;\n}\n\nexport interface WorkspaceSummary {\n id: string;\n name: string;\n slug: string;\n membershipId: string;\n status?: 'active' | 'suspended' | 'deleted';\n}\n"],"names":[],"mappings":"AAoBA,WAMC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/session-auth.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,KAAK,iBAAiB,EAAe,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAC,oBAAoB,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAG7E,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,eAAO,MAAM,mBAAmB,8BAA+B,CAAC;AAChE,eAAO,MAAM,sBAAsB,iCAAkC,CAAC;AAEtE,KAAK,uBAAuB,GAAG,iBAAiB,CAC9C,oBAAoB,EACpB,KAAK,EACL,oBAAoB,EACpB,OAAO,mBAAmB,CAC3B,CAAC;AAEF,KAAK,0BAA0B,GAAG,iBAAiB,CACjD,cAAc,EACd,KAAK,EACL,cAAc,EACd,OAAO,sBAAsB,CAC9B,CAAC;AAEF,wBAAsB,kBAAkB,CACtC,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"session-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/session-auth.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,KAAK,iBAAiB,EAAe,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAC,oBAAoB,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAG7E,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,eAAO,MAAM,mBAAmB,8BAA+B,CAAC;AAChE,eAAO,MAAM,sBAAsB,iCAAkC,CAAC;AAEtE,KAAK,uBAAuB,GAAG,iBAAiB,CAC9C,oBAAoB,EACpB,KAAK,EACL,oBAAoB,EACpB,OAAO,mBAAmB,CAC3B,CAAC;AAEF,KAAK,0BAA0B,GAAG,iBAAiB,CACjD,cAAc,EACd,KAAK,EACL,cAAc,EACd,OAAO,sBAAsB,CAC9B,CAAC;AAEF,wBAAsB,kBAAkB,CACtC,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAezB;AAED,wBAAgB,uBAAuB,IAAI,uBAAuB,CAOjE;AAED,wBAAgB,0BAA0B,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,0BAA0B,CAOrF;AAED,wBAAsB,2BAA2B,CAC/C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAM/B"}
|
|
@@ -24,6 +24,7 @@ export async function listUserWorkspaces(token, signal) {
|
|
|
24
24
|
memberships: response.memberships.map((membership)=>({
|
|
25
25
|
id: membership.workspace_id,
|
|
26
26
|
name: membership.workspace_name,
|
|
27
|
+
slug: membership.workspace_slug,
|
|
27
28
|
membershipId: membership.id,
|
|
28
29
|
status: membership.workspace_status
|
|
29
30
|
}))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hooks/api/session-auth.ts"],"sourcesContent":["import {loginResponseSchema} from '@shipfox/api-auth-dto';\nimport {listUserWorkspacesResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {type FetchQueryOptions, queryOptions} from '@tanstack/react-query';\nimport type {AuthenticatedSession, WorkspaceSummary} from '#core/session.js';\nimport {toAuthenticatedSession} from './session-mapper.js';\n\nexport interface UserWorkspaces {\n memberships: WorkspaceSummary[];\n}\n\nexport const authRefreshQueryKey = ['auth', 'refresh'] as const;\nexport const userWorkspacesQueryKey = ['workspaces', 'mine'] as const;\n\ntype AuthRefreshQueryOptions = FetchQueryOptions<\n AuthenticatedSession,\n Error,\n AuthenticatedSession,\n typeof authRefreshQueryKey\n>;\n\ntype UserWorkspacesQueryOptions = FetchQueryOptions<\n UserWorkspaces,\n Error,\n UserWorkspaces,\n typeof userWorkspacesQueryKey\n>;\n\nexport async function listUserWorkspaces(\n token?: string,\n signal?: AbortSignal,\n): Promise<UserWorkspaces> {\n const response = await checkedApiRequest(\n listUserWorkspacesResponseSchema,\n '/workspaces',\n token ? {headers: {authorization: `Bearer ${token}`}, signal} : {signal},\n );\n return {\n memberships: response.memberships.map((membership) => ({\n id: membership.workspace_id,\n name: membership.workspace_name,\n membershipId: membership.id,\n status: membership.workspace_status,\n })),\n };\n}\n\nexport function authRefreshQueryOptions(): AuthRefreshQueryOptions {\n return queryOptions({\n queryKey: authRefreshQueryKey,\n queryFn: ({signal}) => refreshAuthenticatedSession(signal),\n retry: false,\n staleTime: 0,\n });\n}\n\nexport function userWorkspacesQueryOptions(token?: string): UserWorkspacesQueryOptions {\n return queryOptions({\n queryKey: userWorkspacesQueryKey,\n queryFn: ({signal}) => listUserWorkspaces(token, signal),\n retry: false,\n staleTime: 0,\n });\n}\n\nexport async function refreshAuthenticatedSession(\n signal?: AbortSignal,\n): Promise<AuthenticatedSession> {\n const response = await checkedApiRequest(loginResponseSchema, '/auth/refresh', {\n method: 'POST',\n signal,\n });\n return toAuthenticatedSession(response);\n}\n"],"names":["loginResponseSchema","listUserWorkspacesResponseSchema","checkedApiRequest","queryOptions","toAuthenticatedSession","authRefreshQueryKey","userWorkspacesQueryKey","listUserWorkspaces","token","signal","response","headers","authorization","memberships","map","membership","id","workspace_id","name","workspace_name","membershipId","status","workspace_status","authRefreshQueryOptions","queryKey","queryFn","refreshAuthenticatedSession","retry","staleTime","userWorkspacesQueryOptions","method"],"mappings":"AAAA,SAAQA,mBAAmB,QAAO,wBAAwB;AAC1D,SAAQC,gCAAgC,QAAO,8BAA8B;AAC7E,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAgCC,YAAY,QAAO,wBAAwB;AAE3E,SAAQC,sBAAsB,QAAO,sBAAsB;AAM3D,OAAO,MAAMC,sBAAsB;IAAC;IAAQ;CAAU,CAAU;AAChE,OAAO,MAAMC,yBAAyB;IAAC;IAAc;CAAO,CAAU;AAgBtE,OAAO,eAAeC,mBACpBC,KAAc,EACdC,MAAoB;IAEpB,MAAMC,WAAW,MAAMR,kBACrBD,kCACA,eACAO,QAAQ;QAACG,SAAS;YAACC,eAAe,CAAC,OAAO,EAAEJ,OAAO;QAAA;QAAGC;IAAM,IAAI;QAACA;IAAM;IAEzE,OAAO;QACLI,aAAaH,SAASG,WAAW,CAACC,GAAG,CAAC,CAACC,aAAgB,CAAA;gBACrDC,IAAID,WAAWE,YAAY;gBAC3BC,MAAMH,WAAWI,cAAc;gBAC/BC,
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/api/session-auth.ts"],"sourcesContent":["import {loginResponseSchema} from '@shipfox/api-auth-dto';\nimport {listUserWorkspacesResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {type FetchQueryOptions, queryOptions} from '@tanstack/react-query';\nimport type {AuthenticatedSession, WorkspaceSummary} from '#core/session.js';\nimport {toAuthenticatedSession} from './session-mapper.js';\n\nexport interface UserWorkspaces {\n memberships: WorkspaceSummary[];\n}\n\nexport const authRefreshQueryKey = ['auth', 'refresh'] as const;\nexport const userWorkspacesQueryKey = ['workspaces', 'mine'] as const;\n\ntype AuthRefreshQueryOptions = FetchQueryOptions<\n AuthenticatedSession,\n Error,\n AuthenticatedSession,\n typeof authRefreshQueryKey\n>;\n\ntype UserWorkspacesQueryOptions = FetchQueryOptions<\n UserWorkspaces,\n Error,\n UserWorkspaces,\n typeof userWorkspacesQueryKey\n>;\n\nexport async function listUserWorkspaces(\n token?: string,\n signal?: AbortSignal,\n): Promise<UserWorkspaces> {\n const response = await checkedApiRequest(\n listUserWorkspacesResponseSchema,\n '/workspaces',\n token ? {headers: {authorization: `Bearer ${token}`}, signal} : {signal},\n );\n return {\n memberships: response.memberships.map((membership) => ({\n id: membership.workspace_id,\n name: membership.workspace_name,\n slug: membership.workspace_slug,\n membershipId: membership.id,\n status: membership.workspace_status,\n })),\n };\n}\n\nexport function authRefreshQueryOptions(): AuthRefreshQueryOptions {\n return queryOptions({\n queryKey: authRefreshQueryKey,\n queryFn: ({signal}) => refreshAuthenticatedSession(signal),\n retry: false,\n staleTime: 0,\n });\n}\n\nexport function userWorkspacesQueryOptions(token?: string): UserWorkspacesQueryOptions {\n return queryOptions({\n queryKey: userWorkspacesQueryKey,\n queryFn: ({signal}) => listUserWorkspaces(token, signal),\n retry: false,\n staleTime: 0,\n });\n}\n\nexport async function refreshAuthenticatedSession(\n signal?: AbortSignal,\n): Promise<AuthenticatedSession> {\n const response = await checkedApiRequest(loginResponseSchema, '/auth/refresh', {\n method: 'POST',\n signal,\n });\n return toAuthenticatedSession(response);\n}\n"],"names":["loginResponseSchema","listUserWorkspacesResponseSchema","checkedApiRequest","queryOptions","toAuthenticatedSession","authRefreshQueryKey","userWorkspacesQueryKey","listUserWorkspaces","token","signal","response","headers","authorization","memberships","map","membership","id","workspace_id","name","workspace_name","slug","workspace_slug","membershipId","status","workspace_status","authRefreshQueryOptions","queryKey","queryFn","refreshAuthenticatedSession","retry","staleTime","userWorkspacesQueryOptions","method"],"mappings":"AAAA,SAAQA,mBAAmB,QAAO,wBAAwB;AAC1D,SAAQC,gCAAgC,QAAO,8BAA8B;AAC7E,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAgCC,YAAY,QAAO,wBAAwB;AAE3E,SAAQC,sBAAsB,QAAO,sBAAsB;AAM3D,OAAO,MAAMC,sBAAsB;IAAC;IAAQ;CAAU,CAAU;AAChE,OAAO,MAAMC,yBAAyB;IAAC;IAAc;CAAO,CAAU;AAgBtE,OAAO,eAAeC,mBACpBC,KAAc,EACdC,MAAoB;IAEpB,MAAMC,WAAW,MAAMR,kBACrBD,kCACA,eACAO,QAAQ;QAACG,SAAS;YAACC,eAAe,CAAC,OAAO,EAAEJ,OAAO;QAAA;QAAGC;IAAM,IAAI;QAACA;IAAM;IAEzE,OAAO;QACLI,aAAaH,SAASG,WAAW,CAACC,GAAG,CAAC,CAACC,aAAgB,CAAA;gBACrDC,IAAID,WAAWE,YAAY;gBAC3BC,MAAMH,WAAWI,cAAc;gBAC/BC,MAAML,WAAWM,cAAc;gBAC/BC,cAAcP,WAAWC,EAAE;gBAC3BO,QAAQR,WAAWS,gBAAgB;YACrC,CAAA;IACF;AACF;AAEA,OAAO,SAASC;IACd,OAAOtB,aAAa;QAClBuB,UAAUrB;QACVsB,SAAS,CAAC,EAAClB,MAAM,EAAC,GAAKmB,4BAA4BnB;QACnDoB,OAAO;QACPC,WAAW;IACb;AACF;AAEA,OAAO,SAASC,2BAA2BvB,KAAc;IACvD,OAAOL,aAAa;QAClBuB,UAAUpB;QACVqB,SAAS,CAAC,EAAClB,MAAM,EAAC,GAAKF,mBAAmBC,OAAOC;QACjDoB,OAAO;QACPC,WAAW;IACb;AACF;AAEA,OAAO,eAAeF,4BACpBnB,MAAoB;IAEpB,MAAMC,WAAW,MAAMR,kBAAkBF,qBAAqB,iBAAiB;QAC7EgC,QAAQ;QACRvB;IACF;IACA,OAAOL,uBAAuBM;AAChC"}
|
|
@@ -2,12 +2,12 @@ import { useAuthState } from './auth.js';
|
|
|
2
2
|
import { parseWorkspaceParams, useRouteParams } from './route-inputs.js';
|
|
3
3
|
export function useActiveWorkspace() {
|
|
4
4
|
const workspace = useMaybeActiveWorkspace();
|
|
5
|
-
if (!workspace) throw new Error('useActiveWorkspace called outside a /
|
|
5
|
+
if (!workspace) throw new Error('useActiveWorkspace called outside a /w/$workspaceSlug route');
|
|
6
6
|
return workspace;
|
|
7
7
|
}
|
|
8
8
|
export function useMaybeActiveWorkspace() {
|
|
9
|
-
const {
|
|
10
|
-
return useAuthState().workspaces.find((workspace)=>workspace.
|
|
9
|
+
const { workspaceSlug } = useRouteParams(parseWorkspaceParams);
|
|
10
|
+
return useAuthState().workspaces.find((workspace)=>workspace.slug === workspaceSlug);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
//# sourceMappingURL=active-workspace.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/runtime/active-workspace.ts"],"sourcesContent":["import {useAuthState, type Workspace} from './auth.js';\nimport {parseWorkspaceParams, useRouteParams} from './route-inputs.js';\n\nexport function useActiveWorkspace(): Workspace {\n const workspace = useMaybeActiveWorkspace();\n if (!workspace) throw new Error('useActiveWorkspace called outside a /
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/active-workspace.ts"],"sourcesContent":["import {useAuthState, type Workspace} from './auth.js';\nimport {parseWorkspaceParams, useRouteParams} from './route-inputs.js';\n\nexport function useActiveWorkspace(): Workspace {\n const workspace = useMaybeActiveWorkspace();\n if (!workspace) throw new Error('useActiveWorkspace called outside a /w/$workspaceSlug route');\n return workspace;\n}\n\nexport function useMaybeActiveWorkspace(): Workspace | undefined {\n const {workspaceSlug} = useRouteParams(parseWorkspaceParams);\n return useAuthState().workspaces.find((workspace) => workspace.slug === workspaceSlug);\n}\n"],"names":["useAuthState","parseWorkspaceParams","useRouteParams","useActiveWorkspace","workspace","useMaybeActiveWorkspace","Error","workspaceSlug","workspaces","find","slug"],"mappings":"AAAA,SAAQA,YAAY,QAAuB,YAAY;AACvD,SAAQC,oBAAoB,EAAEC,cAAc,QAAO,oBAAoB;AAEvE,OAAO,SAASC;IACd,MAAMC,YAAYC;IAClB,IAAI,CAACD,WAAW,MAAM,IAAIE,MAAM;IAChC,OAAOF;AACT;AAEA,OAAO,SAASC;IACd,MAAM,EAACE,aAAa,EAAC,GAAGL,eAAeD;IACvC,OAAOD,eAAeQ,UAAU,CAACC,IAAI,CAAC,CAACL,YAAcA,UAAUM,IAAI,KAAKH;AAC1E"}
|