@shipfox/client-shell 2.0.0 → 3.0.1
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/assets/apple-touch-icon.png +0 -0
- package/assets/favicon-96x96.png +0 -0
- package/assets/favicon.ico +0 -0
- package/assets/favicon.svg +1 -0
- package/assets/site.webmanifest +22 -0
- package/assets/web-app-manifest-192x192.png +0 -0
- package/assets/web-app-manifest-512x512.png +0 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +1 -0
- package/dist/vite/index.js.map +1 -1
- package/dist/vite/manifest.d.ts +3 -0
- package/dist/vite/manifest.d.ts.map +1 -0
- package/dist/vite/manifest.js +140 -0
- package/dist/vite/manifest.js.map +1 -0
- package/package.json +11 -5
- package/.turbo/turbo-build.log +0 -2
- package/.turbo/turbo-type$colon$emit.log +0 -1
- package/.turbo/turbo-type.log +0 -1
- package/CHANGELOG.md +0 -59
- package/src/components/main-layout.tsx +0 -45
- package/src/components/nav-bar.tsx +0 -41
- package/src/components/nav-tabs.test.tsx +0 -60
- package/src/components/nav-tabs.tsx +0 -47
- package/src/components/not-found-page.tsx +0 -14
- package/src/components/settings-nav.tsx +0 -35
- package/src/components/user-menu.tsx +0 -59
- package/src/components/workspace-crumb.tsx +0 -43
- package/src/components/workspace-switcher.tsx +0 -75
- package/src/compose/compose-routes.test.ts +0 -112
- package/src/compose/compose-routes.ts +0 -60
- package/src/compose/errors.ts +0 -54
- package/src/compose/merge-config.test.ts +0 -26
- package/src/compose/merge-config.ts +0 -27
- package/src/compose/normalize-route-path.ts +0 -6
- package/src/compose/validate-providers.test.ts +0 -31
- package/src/compose/validate-providers.ts +0 -36
- package/src/compose/validate-registries.test.ts +0 -92
- package/src/compose/validate-registries.ts +0 -61
- package/src/config/config-screen.test.tsx +0 -37
- package/src/config/shell-config.ts +0 -10
- package/src/contract.ts +0 -47
- package/src/index.ts +0 -1
- package/src/runtime/active-workspace.ts +0 -13
- package/src/runtime/anchor-paths.ts +0 -18
- package/src/runtime/anchors.tsx +0 -106
- package/src/runtime/assemble-route-tree.ts +0 -42
- package/src/runtime/auth.tsx +0 -213
- package/src/runtime/chrome-context.tsx +0 -22
- package/src/runtime/compose-client-app.tsx +0 -122
- package/src/runtime/define-route.test.ts +0 -15
- package/src/runtime/define-route.ts +0 -33
- package/src/runtime/index.ts +0 -19
- package/src/runtime/last-workspace.ts +0 -22
- package/src/runtime/nav-order.ts +0 -1
- package/src/runtime/provider-stack.tsx +0 -39
- package/src/runtime/providers.test.tsx +0 -51
- package/src/runtime/registries.test.tsx +0 -100
- package/src/runtime/registries.ts +0 -42
- package/src/runtime/router-context.ts +0 -9
- package/src/runtime/routes.test.tsx +0 -48
- package/src/runtime/workspace-setup.tsx +0 -61
- package/src/testing/index.test.tsx +0 -57
- package/src/testing/index.tsx +0 -51
- package/src/vite/evaluate-features.test.ts +0 -89
- package/src/vite/evaluate-features.ts +0 -114
- package/src/vite/generate.test.ts +0 -22
- package/src/vite/generate.ts +0 -99
- package/src/vite/generated-router.test.tsx +0 -58
- package/src/vite/index.ts +0 -1
- package/src/vite/plugin.test.ts +0 -231
- package/src/vite/plugin.ts +0 -101
- package/src/vite/validate.test.ts +0 -166
- package/test/default-route-impl.tsx +0 -3
- package/test/external/FINDINGS.md +0 -41
- package/test/external/README.md +0 -37
- package/test/external/fixture/index.html +0 -12
- package/test/external/fixture/package.json +0 -31
- package/test/external/fixture/src/app.fixture.tsx +0 -104
- package/test/external/fixture/src/features/external-settings.tsx +0 -19
- package/test/external/fixture/src/features/login-override.tsx +0 -9
- package/test/external/fixture/src/features.collision.ts +0 -15
- package/test/external/fixture/src/features.ts +0 -50
- package/test/external/fixture/src/link-typecheck.tsx +0 -10
- package/test/external/fixture/src/main.tsx +0 -86
- package/test/external/fixture/src/provider.ts +0 -45
- package/test/external/fixture/tsconfig.json +0 -15
- package/test/external/fixture/vite.config.ts +0 -21
- package/test/external/fixture/vitest.config.ts +0 -17
- package/test/external/verify.mjs +0 -488
- package/test/fixtures/features.ts +0 -56
- package/test/named-route-impl.tsx +0 -3
- package/test/render.tsx +0 -67
- package/test/search-route-impl.tsx +0 -7
- package/test/setup.ts +0 -3
- package/test/typecheck/shipfox-app.gen.ts +0 -85
- package/test/typecheck/types.tsx +0 -23
- package/tsconfig.build.json +0 -10
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -1
- package/tsconfig.test.json +0 -12
- package/vitest.config.ts +0 -22
package/dist/vite/index.d.ts
CHANGED
package/dist/vite/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,qBAAqB,EAAC,MAAM,eAAe,CAAC;AACpD,OAAO,EAAC,wBAAwB,EAAC,MAAM,aAAa,CAAC"}
|
package/dist/vite/index.js
CHANGED
package/dist/vite/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["export {shipfoxClientComposition} from './plugin.js';\n"],"names":["shipfoxClientComposition"],"mappings":"AAAA,SAAQA,wBAAwB,QAAO,cAAc"}
|
|
1
|
+
{"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["export {shipfoxClientManifest} from './manifest.js';\nexport {shipfoxClientComposition} from './plugin.js';\n"],"names":["shipfoxClientManifest","shipfoxClientComposition"],"mappings":"AAAA,SAAQA,qBAAqB,QAAO,gBAAgB;AACpD,SAAQC,wBAAwB,QAAO,cAAc"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/vite/manifest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,MAAM,EAAiB,MAAM,MAAM,CAAC;AAqBjD,wBAAgB,qBAAqB,IAAI,MAAM,CAwF9C"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
|
+
import { extname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const assetNames = [
|
|
5
|
+
'favicon.ico',
|
|
6
|
+
'favicon.svg',
|
|
7
|
+
'favicon-96x96.png',
|
|
8
|
+
'apple-touch-icon.png',
|
|
9
|
+
'site.webmanifest',
|
|
10
|
+
'web-app-manifest-192x192.png',
|
|
11
|
+
'web-app-manifest-512x512.png'
|
|
12
|
+
];
|
|
13
|
+
const assetDirectory = fileURLToPath(new URL('../../assets/', import.meta.url));
|
|
14
|
+
const contentTypes = {
|
|
15
|
+
'.ico': 'image/x-icon',
|
|
16
|
+
'.png': 'image/png',
|
|
17
|
+
'.svg': 'image/svg+xml',
|
|
18
|
+
'.webmanifest': 'application/manifest+json'
|
|
19
|
+
};
|
|
20
|
+
export function shipfoxClientManifest() {
|
|
21
|
+
let config;
|
|
22
|
+
function assetPath(name) {
|
|
23
|
+
return resolve(assetDirectory, name);
|
|
24
|
+
}
|
|
25
|
+
async function assertNoPublicAssetConflicts() {
|
|
26
|
+
const publicDirectory = config?.publicDir;
|
|
27
|
+
if (!publicDirectory) return;
|
|
28
|
+
const conflicts = (await Promise.all(assetNames.map((name)=>{
|
|
29
|
+
const publicAsset = resolve(publicDirectory, name);
|
|
30
|
+
if (publicAsset === assetPath(name)) return undefined;
|
|
31
|
+
return access(publicAsset).then(()=>name).catch(()=>undefined);
|
|
32
|
+
}))).filter((name)=>name !== undefined);
|
|
33
|
+
if (conflicts.length === 0) return;
|
|
34
|
+
throw new Error(`shipfoxClientManifest() owns ${conflicts.map((name)=>`/${name}`).join(', ')}. Remove the conflicting files from ${publicDirectory}.`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
name: 'shipfox-client-manifest',
|
|
38
|
+
async configResolved (resolvedConfig) {
|
|
39
|
+
config = resolvedConfig;
|
|
40
|
+
await assertNoPublicAssetConflicts();
|
|
41
|
+
},
|
|
42
|
+
async buildStart () {
|
|
43
|
+
if (config?.command !== 'build') return;
|
|
44
|
+
for (const name of assetNames){
|
|
45
|
+
this.emitFile({
|
|
46
|
+
type: 'asset',
|
|
47
|
+
fileName: name,
|
|
48
|
+
source: await readFile(assetPath(name))
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
configureServer (server) {
|
|
53
|
+
server.middlewares.use(async (request, response, next)=>{
|
|
54
|
+
const name = assetNames.find((assetName)=>request.url?.split('?')[0] === `/${assetName}`);
|
|
55
|
+
if (!name) return next();
|
|
56
|
+
try {
|
|
57
|
+
const content = await readFile(assetPath(name));
|
|
58
|
+
response.setHeader('Content-Type', contentTypes[extname(name)] ?? 'application/octet-stream');
|
|
59
|
+
response.end(content);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
next(error);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
transformIndexHtml: {
|
|
66
|
+
order: 'pre',
|
|
67
|
+
handler () {
|
|
68
|
+
return [
|
|
69
|
+
{
|
|
70
|
+
tag: 'title',
|
|
71
|
+
children: 'Shipfox',
|
|
72
|
+
injectTo: 'head'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
tag: 'link',
|
|
76
|
+
attrs: {
|
|
77
|
+
rel: 'icon',
|
|
78
|
+
type: 'image/png',
|
|
79
|
+
href: '/favicon-96x96.png',
|
|
80
|
+
sizes: '96x96'
|
|
81
|
+
},
|
|
82
|
+
injectTo: 'head'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
tag: 'link',
|
|
86
|
+
attrs: {
|
|
87
|
+
rel: 'icon',
|
|
88
|
+
type: 'image/svg+xml',
|
|
89
|
+
href: '/favicon.svg'
|
|
90
|
+
},
|
|
91
|
+
injectTo: 'head'
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
tag: 'link',
|
|
95
|
+
attrs: {
|
|
96
|
+
rel: 'shortcut icon',
|
|
97
|
+
href: '/favicon.ico'
|
|
98
|
+
},
|
|
99
|
+
injectTo: 'head'
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
tag: 'link',
|
|
103
|
+
attrs: {
|
|
104
|
+
rel: 'apple-touch-icon',
|
|
105
|
+
sizes: '180x180',
|
|
106
|
+
href: '/apple-touch-icon.png'
|
|
107
|
+
},
|
|
108
|
+
injectTo: 'head'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
tag: 'link',
|
|
112
|
+
attrs: {
|
|
113
|
+
rel: 'manifest',
|
|
114
|
+
href: '/site.webmanifest'
|
|
115
|
+
},
|
|
116
|
+
injectTo: 'head'
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
tag: 'meta',
|
|
120
|
+
attrs: {
|
|
121
|
+
name: 'apple-mobile-web-app-title',
|
|
122
|
+
content: 'Shipfox'
|
|
123
|
+
},
|
|
124
|
+
injectTo: 'head'
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
tag: 'meta',
|
|
128
|
+
attrs: {
|
|
129
|
+
name: 'theme-color',
|
|
130
|
+
content: '#ff4b00'
|
|
131
|
+
},
|
|
132
|
+
injectTo: 'head'
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/vite/manifest.ts"],"sourcesContent":["import {access, readFile} from 'node:fs/promises';\nimport {extname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport type {Plugin, ResolvedConfig} from 'vite';\n\nconst assetNames = [\n 'favicon.ico',\n 'favicon.svg',\n 'favicon-96x96.png',\n 'apple-touch-icon.png',\n 'site.webmanifest',\n 'web-app-manifest-192x192.png',\n 'web-app-manifest-512x512.png',\n] as const;\n\nconst assetDirectory = fileURLToPath(new URL('../../assets/', import.meta.url));\n\nconst contentTypes: Record<string, string> = {\n '.ico': 'image/x-icon',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.webmanifest': 'application/manifest+json',\n};\n\nexport function shipfoxClientManifest(): Plugin {\n let config: ResolvedConfig | undefined;\n\n function assetPath(name: (typeof assetNames)[number]): string {\n return resolve(assetDirectory, name);\n }\n\n async function assertNoPublicAssetConflicts(): Promise<void> {\n const publicDirectory = config?.publicDir;\n if (!publicDirectory) return;\n const conflicts = (\n await Promise.all(\n assetNames.map((name) => {\n const publicAsset = resolve(publicDirectory, name);\n if (publicAsset === assetPath(name)) return undefined;\n return access(publicAsset)\n .then(() => name)\n .catch(() => undefined);\n }),\n )\n ).filter((name): name is (typeof assetNames)[number] => name !== undefined);\n if (conflicts.length === 0) return;\n throw new Error(\n `shipfoxClientManifest() owns ${conflicts.map((name) => `/${name}`).join(', ')}. Remove the conflicting files from ${publicDirectory}.`,\n );\n }\n\n return {\n name: 'shipfox-client-manifest',\n async configResolved(resolvedConfig) {\n config = resolvedConfig;\n await assertNoPublicAssetConflicts();\n },\n async buildStart() {\n if (config?.command !== 'build') return;\n for (const name of assetNames) {\n this.emitFile({type: 'asset', fileName: name, source: await readFile(assetPath(name))});\n }\n },\n configureServer(server) {\n server.middlewares.use(async (request, response, next) => {\n const name = assetNames.find((assetName) => request.url?.split('?')[0] === `/${assetName}`);\n if (!name) return next();\n\n try {\n const content = await readFile(assetPath(name));\n response.setHeader(\n 'Content-Type',\n contentTypes[extname(name)] ?? 'application/octet-stream',\n );\n response.end(content);\n } catch (error) {\n next(error);\n }\n });\n },\n transformIndexHtml: {\n order: 'pre',\n handler() {\n return [\n {tag: 'title', children: 'Shipfox', injectTo: 'head'},\n {\n tag: 'link',\n attrs: {rel: 'icon', type: 'image/png', href: '/favicon-96x96.png', sizes: '96x96'},\n injectTo: 'head',\n },\n {\n tag: 'link',\n attrs: {rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg'},\n injectTo: 'head',\n },\n {tag: 'link', attrs: {rel: 'shortcut icon', href: '/favicon.ico'}, injectTo: 'head'},\n {\n tag: 'link',\n attrs: {rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png'},\n injectTo: 'head',\n },\n {tag: 'link', attrs: {rel: 'manifest', href: '/site.webmanifest'}, injectTo: 'head'},\n {\n tag: 'meta',\n attrs: {name: 'apple-mobile-web-app-title', content: 'Shipfox'},\n injectTo: 'head',\n },\n {tag: 'meta', attrs: {name: 'theme-color', content: '#ff4b00'}, injectTo: 'head'},\n ];\n },\n },\n };\n}\n"],"names":["access","readFile","extname","resolve","fileURLToPath","assetNames","assetDirectory","URL","url","contentTypes","shipfoxClientManifest","config","assetPath","name","assertNoPublicAssetConflicts","publicDirectory","publicDir","conflicts","Promise","all","map","publicAsset","undefined","then","catch","filter","length","Error","join","configResolved","resolvedConfig","buildStart","command","emitFile","type","fileName","source","configureServer","server","middlewares","use","request","response","next","find","assetName","split","content","setHeader","end","error","transformIndexHtml","order","handler","tag","children","injectTo","attrs","rel","href","sizes"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-shell",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.1",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"type": "module",
|
|
11
11
|
"main": "dist/index.js",
|
|
12
12
|
"types": "dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"assets"
|
|
16
|
+
],
|
|
13
17
|
"imports": {
|
|
14
18
|
"#*": "./dist/*",
|
|
15
19
|
"#test/*": "./test/*"
|
|
@@ -38,10 +42,10 @@
|
|
|
38
42
|
"tsx": "^4.22.4",
|
|
39
43
|
"framer-motion": "^12.23.24",
|
|
40
44
|
"@shipfox/api-auth-dto": "6.0.0",
|
|
41
|
-
"@shipfox/client-api": "1.0.0",
|
|
42
45
|
"@shipfox/api-workspaces-dto": "6.0.0",
|
|
43
|
-
"@shipfox/client-
|
|
44
|
-
"@shipfox/
|
|
46
|
+
"@shipfox/client-api": "1.0.0",
|
|
47
|
+
"@shipfox/client-config": "3.0.1",
|
|
48
|
+
"@shipfox/react-ui": "0.3.5"
|
|
45
49
|
},
|
|
46
50
|
"peerDependencies": {
|
|
47
51
|
"@storybook/react": "^10.4.6",
|
|
@@ -72,11 +76,13 @@
|
|
|
72
76
|
"react-dom": "^19.1.1",
|
|
73
77
|
"vite": "^8.0.3",
|
|
74
78
|
"zod": "^4.4.3",
|
|
79
|
+
"@shipfox/application-release": "0.1.4",
|
|
75
80
|
"@shipfox/biome": "1.8.2",
|
|
76
81
|
"@shipfox/client-test-setup": "0.0.3",
|
|
77
82
|
"@shipfox/swc": "1.2.6",
|
|
78
83
|
"@shipfox/ts-config": "1.3.8",
|
|
79
84
|
"@shipfox/typescript": "1.1.7",
|
|
85
|
+
"@shipfox/tool-utils": "1.2.0",
|
|
80
86
|
"@shipfox/vitest": "1.2.3"
|
|
81
87
|
},
|
|
82
88
|
"scripts": {
|
|
@@ -84,7 +90,7 @@
|
|
|
84
90
|
"check": "shipfox-biome-check",
|
|
85
91
|
"check:fix": "shipfox-biome-check --write",
|
|
86
92
|
"test": "shipfox-vitest-run",
|
|
87
|
-
"test:external": "
|
|
93
|
+
"test:external": "node test/external/verify.mjs",
|
|
88
94
|
"test:watch": "shipfox-vitest-watch",
|
|
89
95
|
"type": "shipfox-tsc-check",
|
|
90
96
|
"type:emit": "shipfox-tsc-emit"
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ shipfox-tsc-emit
|
package/.turbo/turbo-type.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ shipfox-tsc-check
|
package/CHANGELOG.md
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 2.0.0
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- 7ac43a4: Consolidates packed-consumer validation around stable publication and composition contracts instead of package-state snapshots.
|
|
8
|
-
- Updated dependencies [e6eba5b]
|
|
9
|
-
- Updated dependencies [ba2e3dc]
|
|
10
|
-
- Updated dependencies [1820feb]
|
|
11
|
-
- Updated dependencies [1b79cda]
|
|
12
|
-
- Updated dependencies [c2db8c3]
|
|
13
|
-
- Updated dependencies [112c0fa]
|
|
14
|
-
- Updated dependencies [326f4c0]
|
|
15
|
-
- Updated dependencies [4a91956]
|
|
16
|
-
- @shipfox/api-auth-dto@6.0.0
|
|
17
|
-
- @shipfox/react-ui@0.3.3
|
|
18
|
-
- @shipfox/api-workspaces-dto@6.0.0
|
|
19
|
-
- @shipfox/client-config@2.0.0
|
|
20
|
-
|
|
21
|
-
## 1.0.0
|
|
22
|
-
|
|
23
|
-
### Patch Changes
|
|
24
|
-
|
|
25
|
-
- 47809a2: Hardens packed client composition validation against release artifacts, undeclared route packages, and optional Storybook testing peers.
|
|
26
|
-
- bb037af: Resolves workspace packages from source during development while published consumers continue to use compiled output.
|
|
27
|
-
- 5c63a2a: Validates the published default client composition from a clean external Vite consumer and fixes typed access to the events settings route.
|
|
28
|
-
- d8658ba: Prevents composition builds from reading resolver IDs that include Vite version queries.
|
|
29
|
-
- Updated dependencies [bb037af]
|
|
30
|
-
- @shipfox/api-auth-dto@5.0.0
|
|
31
|
-
- @shipfox/api-workspaces-dto@5.0.0
|
|
32
|
-
- @shipfox/client-api@1.0.0
|
|
33
|
-
- @shipfox/client-config@1.0.0
|
|
34
|
-
- @shipfox/react-ui@0.3.2
|
|
35
|
-
|
|
36
|
-
## 0.2.0
|
|
37
|
-
|
|
38
|
-
### Minor Changes
|
|
39
|
-
|
|
40
|
-
- 3d064b8: Publishes the client runtime closure with shell, feature, route, Vite, and testing contracts.
|
|
41
|
-
- 6bc2e45: Adds the composable upstream client shell, feature catalog, and route manifests for every client feature.
|
|
42
|
-
|
|
43
|
-
### Patch Changes
|
|
44
|
-
|
|
45
|
-
- Updated dependencies [3d064b8]
|
|
46
|
-
- @shipfox/client-api@0.2.0
|
|
47
|
-
- @shipfox/client-config@0.2.0
|
|
48
|
-
|
|
49
|
-
## 0.0.2
|
|
50
|
-
|
|
51
|
-
### Patch Changes
|
|
52
|
-
|
|
53
|
-
- Updated dependencies [c18d624]
|
|
54
|
-
- @shipfox/react-ui@0.3.1
|
|
55
|
-
- @shipfox/client-config@0.0.2
|
|
56
|
-
|
|
57
|
-
## 0.0.1
|
|
58
|
-
|
|
59
|
-
- Add the private candidate client composition shell.
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {FullPageLoader} from '@shipfox/react-ui/loader';
|
|
2
|
-
import {Outlet, useMatches, useParams} from '@tanstack/react-router';
|
|
3
|
-
import type {NavTabEntry} from '#contract.js';
|
|
4
|
-
import {useMaybeActiveWorkspace} from '#runtime/active-workspace.js';
|
|
5
|
-
import {NavBar} from './nav-bar.js';
|
|
6
|
-
import {NavTabs} from './nav-tabs.js';
|
|
7
|
-
|
|
8
|
-
declare module '@tanstack/react-router' {
|
|
9
|
-
interface StaticDataRouteOption {
|
|
10
|
-
layout?: 'full-bleed';
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function MainLayout({
|
|
15
|
-
navigation,
|
|
16
|
-
hideProjectNavigation = false,
|
|
17
|
-
}: {
|
|
18
|
-
navigation: readonly NavTabEntry[];
|
|
19
|
-
hideProjectNavigation?: boolean;
|
|
20
|
-
}) {
|
|
21
|
-
const workspace = useMaybeActiveWorkspace();
|
|
22
|
-
const {pid} = useParams({strict: false}) as {pid?: string};
|
|
23
|
-
const matches = useMatches();
|
|
24
|
-
if (!workspace) return <FullPageLoader />;
|
|
25
|
-
const fullBleed = matches.some((match) => match.staticData.layout === 'full-bleed');
|
|
26
|
-
return (
|
|
27
|
-
<div className="h-screen w-full flex flex-col bg-background-subtle-base">
|
|
28
|
-
<NavBar hideProjectNavigation={hideProjectNavigation} />
|
|
29
|
-
{hideProjectNavigation ? undefined : (
|
|
30
|
-
<NavTabs entries={navigation} scope={pid ? 'project' : 'workspace'} />
|
|
31
|
-
)}
|
|
32
|
-
{fullBleed ? (
|
|
33
|
-
<main className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
|
34
|
-
<Outlet />
|
|
35
|
-
</main>
|
|
36
|
-
) : (
|
|
37
|
-
<main className="flex-1 overflow-auto">
|
|
38
|
-
<div className="max-w-[1120px] mx-auto px-24 py-32">
|
|
39
|
-
<Outlet />
|
|
40
|
-
</div>
|
|
41
|
-
</main>
|
|
42
|
-
)}
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import {Logo} from '@shipfox/react-ui/logo';
|
|
2
|
-
import {Link} from '@tanstack/react-router';
|
|
3
|
-
import {useActiveWorkspace} from '#runtime/active-workspace.js';
|
|
4
|
-
import {useChrome} from '#runtime/chrome-context.js';
|
|
5
|
-
import {UserMenu} from './user-menu.js';
|
|
6
|
-
import {WorkspaceCrumb} from './workspace-crumb.js';
|
|
7
|
-
|
|
8
|
-
export function NavBar({hideProjectNavigation = false}: {hideProjectNavigation?: boolean}) {
|
|
9
|
-
const workspace = useActiveWorkspace();
|
|
10
|
-
const {ProjectBreadcrumb} = useChrome();
|
|
11
|
-
return (
|
|
12
|
-
<header className="sticky top-0 z-30 h-56 px-16 flex items-center gap-12 bg-background-subtle-base border-b border-border-neutral-base shrink-0">
|
|
13
|
-
<Link
|
|
14
|
-
to="/"
|
|
15
|
-
aria-label="Shipfox home"
|
|
16
|
-
className="rounded-6 focus-visible:outline-none focus-visible:shadow-button-neutral-focus"
|
|
17
|
-
>
|
|
18
|
-
{hideProjectNavigation ? (
|
|
19
|
-
<>
|
|
20
|
-
<Logo variant="mark" alt="" className="sm:hidden" />
|
|
21
|
-
<Logo variant="wordmark" alt="" className="hidden sm:block" />
|
|
22
|
-
</>
|
|
23
|
-
) : (
|
|
24
|
-
<Logo variant="wordmark" alt="" />
|
|
25
|
-
)}
|
|
26
|
-
</Link>
|
|
27
|
-
<span className="h-20 w-px bg-border-neutral-base" aria-hidden="true" />
|
|
28
|
-
<WorkspaceCrumb workspace={workspace} compact={hideProjectNavigation} />
|
|
29
|
-
{hideProjectNavigation ? undefined : (
|
|
30
|
-
<>
|
|
31
|
-
<span className="text-foreground-neutral-muted" aria-hidden="true">
|
|
32
|
-
/
|
|
33
|
-
</span>
|
|
34
|
-
<ProjectBreadcrumb />
|
|
35
|
-
</>
|
|
36
|
-
)}
|
|
37
|
-
<div className="flex-1" />
|
|
38
|
-
<UserMenu />
|
|
39
|
-
</header>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createMemoryHistory,
|
|
3
|
-
createRootRoute,
|
|
4
|
-
createRoute,
|
|
5
|
-
createRouter,
|
|
6
|
-
Outlet,
|
|
7
|
-
RouterProvider,
|
|
8
|
-
} from '@tanstack/react-router';
|
|
9
|
-
import {render, screen} from '@testing-library/react';
|
|
10
|
-
import type {NavTabEntry} from '#contract.js';
|
|
11
|
-
import {NavTabs} from './nav-tabs.js';
|
|
12
|
-
|
|
13
|
-
const entries: readonly NavTabEntry[] = [
|
|
14
|
-
{
|
|
15
|
-
id: 'projects',
|
|
16
|
-
scope: 'workspace',
|
|
17
|
-
label: 'Projects',
|
|
18
|
-
to: '/workspaces/$wid/projects',
|
|
19
|
-
exact: true,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
id: 'settings',
|
|
23
|
-
scope: 'workspace',
|
|
24
|
-
label: 'Settings',
|
|
25
|
-
to: '/workspaces/$wid/settings',
|
|
26
|
-
},
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
function WorkspaceTabs() {
|
|
30
|
-
return <NavTabs entries={entries} scope="workspace" />;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe('NavTabs', () => {
|
|
34
|
-
test('distinguishes active and inactive workspace tabs', async () => {
|
|
35
|
-
const rootRoute = createRootRoute({component: Outlet});
|
|
36
|
-
const projectsRoute = createRoute({
|
|
37
|
-
getParentRoute: () => rootRoute,
|
|
38
|
-
path: '/workspaces/$wid/projects',
|
|
39
|
-
component: WorkspaceTabs,
|
|
40
|
-
});
|
|
41
|
-
const settingsRoute = createRoute({
|
|
42
|
-
getParentRoute: () => rootRoute,
|
|
43
|
-
path: '/workspaces/$wid/settings',
|
|
44
|
-
component: WorkspaceTabs,
|
|
45
|
-
});
|
|
46
|
-
const router = createRouter({
|
|
47
|
-
history: createMemoryHistory({initialEntries: ['/workspaces/workspace/settings']}),
|
|
48
|
-
routeTree: rootRoute.addChildren([projectsRoute, settingsRoute]),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
render(<RouterProvider router={router} />);
|
|
52
|
-
|
|
53
|
-
const projectsTab = await screen.findByRole('tab', {name: 'Projects'});
|
|
54
|
-
const settingsTab = screen.getByRole('tab', {name: 'Settings'});
|
|
55
|
-
expect(projectsTab).toHaveClass('text-foreground-neutral-muted');
|
|
56
|
-
expect(projectsTab).toHaveAttribute('aria-selected', 'false');
|
|
57
|
-
expect(settingsTab).toHaveClass('text-foreground-neutral-base');
|
|
58
|
-
expect(settingsTab).toHaveAttribute('aria-selected', 'true');
|
|
59
|
-
});
|
|
60
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import {Link, useParams} from '@tanstack/react-router';
|
|
2
|
-
import {useReducedMotion} from 'framer-motion';
|
|
3
|
-
import type {NavTabEntry} from '#contract.js';
|
|
4
|
-
|
|
5
|
-
export function NavTabs({
|
|
6
|
-
entries,
|
|
7
|
-
scope,
|
|
8
|
-
}: {
|
|
9
|
-
entries: readonly NavTabEntry[];
|
|
10
|
-
scope: NavTabEntry['scope'];
|
|
11
|
-
}) {
|
|
12
|
-
const params = useParams({strict: false}) as {wid?: string; pid?: string};
|
|
13
|
-
const reduced = useReducedMotion();
|
|
14
|
-
const tabs = entries.filter((entry) => entry.scope === scope);
|
|
15
|
-
const tabClassName = `h-40 inline-flex items-center px-4 text-sm font-medium transition-colors ${reduced ? '' : 'transition-[border-color]'}`;
|
|
16
|
-
const activeProps = {
|
|
17
|
-
className: 'border-b-2 border-border-highlights-interactive text-foreground-neutral-base',
|
|
18
|
-
'aria-selected': 'true' as const,
|
|
19
|
-
};
|
|
20
|
-
const inactiveProps = {
|
|
21
|
-
className: 'border-b-2 border-transparent text-foreground-neutral-muted',
|
|
22
|
-
'aria-selected': 'false' as const,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div
|
|
27
|
-
role="tablist"
|
|
28
|
-
aria-label={`${scope === 'project' ? 'Project' : 'Workspace'} sections`}
|
|
29
|
-
className="sticky top-56 z-20 h-40 px-16 flex items-end gap-12 bg-background-subtle-base border-b border-border-neutral-base"
|
|
30
|
-
>
|
|
31
|
-
{tabs.map((entry) => (
|
|
32
|
-
<Link
|
|
33
|
-
key={entry.id}
|
|
34
|
-
to={entry.to as never}
|
|
35
|
-
params={params as never}
|
|
36
|
-
role="tab"
|
|
37
|
-
activeOptions={{exact: entry.exact ?? false}}
|
|
38
|
-
activeProps={activeProps}
|
|
39
|
-
inactiveProps={inactiveProps}
|
|
40
|
-
className={tabClassName}
|
|
41
|
-
>
|
|
42
|
-
{entry.label}
|
|
43
|
-
</Link>
|
|
44
|
-
))}
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
2
|
-
import {Link} from '@tanstack/react-router';
|
|
3
|
-
|
|
4
|
-
export function NotFoundPage() {
|
|
5
|
-
return (
|
|
6
|
-
<main className="mx-auto max-w-[960px] px-24 py-48">
|
|
7
|
-
<Header variant="h1">Page not found</Header>
|
|
8
|
-
<Text size="md" className="text-foreground-neutral-muted">
|
|
9
|
-
This Shipfox page does not exist.
|
|
10
|
-
</Text>
|
|
11
|
-
<Link to="/">Go home</Link>
|
|
12
|
-
</main>
|
|
13
|
-
);
|
|
14
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import {Button} from '@shipfox/react-ui/button';
|
|
2
|
-
import {Icon} from '@shipfox/react-ui/icon';
|
|
3
|
-
import {Link, useMatchRoute, useParams} from '@tanstack/react-router';
|
|
4
|
-
import type {SettingsSectionEntry} from '#contract.js';
|
|
5
|
-
|
|
6
|
-
export function SettingsNav({entries}: {entries: readonly SettingsSectionEntry[]}) {
|
|
7
|
-
const params = useParams({strict: false}) as {wid?: string};
|
|
8
|
-
const matchRoute = useMatchRoute();
|
|
9
|
-
if (!params.wid) return null;
|
|
10
|
-
return (
|
|
11
|
-
<nav aria-label="Workspace settings" className="flex flex-col gap-4">
|
|
12
|
-
{entries.map((entry) => {
|
|
13
|
-
const to = `/workspaces/$wid/settings/${entry.pathSegment}`;
|
|
14
|
-
const active = Boolean(matchRoute({to: to as never, params: {wid: params.wid} as never}));
|
|
15
|
-
return (
|
|
16
|
-
<Button
|
|
17
|
-
key={entry.id}
|
|
18
|
-
asChild
|
|
19
|
-
variant={active ? 'secondary' : 'transparent'}
|
|
20
|
-
className="w-full justify-start"
|
|
21
|
-
>
|
|
22
|
-
<Link
|
|
23
|
-
to={to as never}
|
|
24
|
-
params={{wid: params.wid} as never}
|
|
25
|
-
aria-current={active ? 'page' : undefined}
|
|
26
|
-
>
|
|
27
|
-
<Icon name={entry.icon} className="size-16" />
|
|
28
|
-
{entry.label}
|
|
29
|
-
</Link>
|
|
30
|
-
</Button>
|
|
31
|
-
);
|
|
32
|
-
})}
|
|
33
|
-
</nav>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import {Avatar} from '@shipfox/react-ui/avatar';
|
|
2
|
-
import {
|
|
3
|
-
DropdownMenu,
|
|
4
|
-
DropdownMenuContent,
|
|
5
|
-
DropdownMenuItem,
|
|
6
|
-
DropdownMenuLabel,
|
|
7
|
-
DropdownMenuRadioGroup,
|
|
8
|
-
DropdownMenuRadioItem,
|
|
9
|
-
DropdownMenuSeparator,
|
|
10
|
-
DropdownMenuTrigger,
|
|
11
|
-
} from '@shipfox/react-ui/dropdown-menu';
|
|
12
|
-
import {useTheme} from '@shipfox/react-ui/hooks';
|
|
13
|
-
import type {Theme} from '@shipfox/react-ui/theme';
|
|
14
|
-
import {Link} from '@tanstack/react-router';
|
|
15
|
-
import {useAuthState} from '#runtime/auth.js';
|
|
16
|
-
|
|
17
|
-
const themeOptions: Array<{value: Theme; label: string}> = [
|
|
18
|
-
{value: 'light', label: 'Light'},
|
|
19
|
-
{value: 'dark', label: 'Dark'},
|
|
20
|
-
{value: 'system', label: 'System'},
|
|
21
|
-
];
|
|
22
|
-
export function UserMenu() {
|
|
23
|
-
const {user} = useAuthState();
|
|
24
|
-
const {theme, setTheme} = useTheme();
|
|
25
|
-
const email = user?.email ?? '';
|
|
26
|
-
return (
|
|
27
|
-
<DropdownMenu>
|
|
28
|
-
<DropdownMenuTrigger asChild>
|
|
29
|
-
<button
|
|
30
|
-
type="button"
|
|
31
|
-
aria-label="User menu"
|
|
32
|
-
className="rounded-full focus-visible:outline-none focus-visible:shadow-button-neutral-focus"
|
|
33
|
-
>
|
|
34
|
-
<Avatar size="sm" content="letters" fallback={email} />
|
|
35
|
-
</button>
|
|
36
|
-
</DropdownMenuTrigger>
|
|
37
|
-
<DropdownMenuContent align="end" sideOffset={8} className="min-w-[220px]">
|
|
38
|
-
<DropdownMenuLabel className="text-xs text-foreground-neutral-muted truncate">
|
|
39
|
-
{email}
|
|
40
|
-
</DropdownMenuLabel>
|
|
41
|
-
<DropdownMenuSeparator />
|
|
42
|
-
<DropdownMenuLabel className="text-xs text-foreground-neutral-muted">
|
|
43
|
-
Theme
|
|
44
|
-
</DropdownMenuLabel>
|
|
45
|
-
<DropdownMenuRadioGroup value={theme} onValueChange={(value) => setTheme(value as Theme)}>
|
|
46
|
-
{themeOptions.map((option) => (
|
|
47
|
-
<DropdownMenuRadioItem key={option.value} value={option.value}>
|
|
48
|
-
{option.label}
|
|
49
|
-
</DropdownMenuRadioItem>
|
|
50
|
-
))}
|
|
51
|
-
</DropdownMenuRadioGroup>
|
|
52
|
-
<DropdownMenuSeparator />
|
|
53
|
-
<DropdownMenuItem asChild>
|
|
54
|
-
<Link to={'/auth/logout' as never}>Logout</Link>
|
|
55
|
-
</DropdownMenuItem>
|
|
56
|
-
</DropdownMenuContent>
|
|
57
|
-
</DropdownMenu>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {Icon} from '@shipfox/react-ui/icon';
|
|
2
|
-
import {Popover, PopoverContent, PopoverTrigger} from '@shipfox/react-ui/popover';
|
|
3
|
-
import {Link} from '@tanstack/react-router';
|
|
4
|
-
import {useState} from 'react';
|
|
5
|
-
import type {Workspace} from '#runtime/auth.js';
|
|
6
|
-
import {WorkspaceSwitcher} from './workspace-switcher.js';
|
|
7
|
-
|
|
8
|
-
export interface WorkspaceCrumbProps {
|
|
9
|
-
workspace: Workspace;
|
|
10
|
-
compact?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function WorkspaceCrumb({workspace, compact = false}: WorkspaceCrumbProps) {
|
|
14
|
-
const [open, setOpen] = useState(false);
|
|
15
|
-
return (
|
|
16
|
-
<div className="flex items-center">
|
|
17
|
-
<Link
|
|
18
|
-
to="/workspaces/$wid"
|
|
19
|
-
params={{wid: workspace.id}}
|
|
20
|
-
aria-current="page"
|
|
21
|
-
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]'}`}
|
|
22
|
-
>
|
|
23
|
-
{workspace.name}
|
|
24
|
-
</Link>
|
|
25
|
-
<Popover open={open} onOpenChange={setOpen}>
|
|
26
|
-
<PopoverTrigger asChild>
|
|
27
|
-
<button
|
|
28
|
-
type="button"
|
|
29
|
-
aria-label="Switch workspace"
|
|
30
|
-
aria-haspopup="listbox"
|
|
31
|
-
aria-expanded={open}
|
|
32
|
-
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"
|
|
33
|
-
>
|
|
34
|
-
<Icon name="arrowDownSLine" className="size-16" />
|
|
35
|
-
</button>
|
|
36
|
-
</PopoverTrigger>
|
|
37
|
-
<PopoverContent className="w-[280px] p-0" align="start" sideOffset={8}>
|
|
38
|
-
<WorkspaceSwitcher activeWorkspaceId={workspace.id} onSelect={() => setOpen(false)} />
|
|
39
|
-
</PopoverContent>
|
|
40
|
-
</Popover>
|
|
41
|
-
</div>
|
|
42
|
-
);
|
|
43
|
-
}
|