@synopackageland/templates 0.9.1 → 0.9.3
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/README.md +1 -1
- package/package.json +1 -1
- package/templates/_shared/AGENTS.md +6 -0
- package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
- package/templates/native-go/backend/go.mod +1 -1
- package/templates/native-go/backend/go.sum +2 -2
- package/templates/native-go-delegate/frontend/test/capability-panels.test.ts +2 -1
- package/templates/native-go-vue/frontend/DESIGN.md +1 -0
- package/templates/native-go-vue/frontend/package-lock.json +12 -12
- package/templates/native-go-vue/frontend/package.json +2 -2
- package/templates/native-go-vue/frontend/src/App.vue +16 -9
- package/templates/native-go-vue/frontend/src/i18n/de.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/en.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/es.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/fr.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/it.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/ja.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/ko.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +0 -1
- package/templates/native-go-vue/frontend/test/apps.test.ts +37 -0
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ An overlay may replace `internal/app/*_none.go` (the CLI deletes the matching
|
|
|
21
21
|
The placeholders `__PACKAGE__` (also the Go module path), `__DISPLAY_NAME__` and
|
|
22
22
|
`__BACKGROUND_ENABLED__` are substituted by `init`.
|
|
23
23
|
|
|
24
|
-
`backend/go.mod` requires `github.com/synopackageland/syno-pkg-land-sdk v0.9.
|
|
24
|
+
`backend/go.mod` requires `github.com/synopackageland/syno-pkg-land-sdk v0.9.4`; that line is
|
|
25
25
|
the single source of the SDK version and `frontend/package.json` pins
|
|
26
26
|
`@synopackageland/sdk` to the same version.
|
|
27
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/templates",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Project templates for synopkgland init: native-go (plain or Vue, with jobs, delegate and root-ops overlays), binary and compose.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {"type": "git", "url": "git+https://github.com/synopackageland/syno-pkg-land-templates.git"},
|
|
@@ -11,6 +11,12 @@ For UI discovery, run `npx --no syno-pkg-land-ui catalog --json` and
|
|
|
11
11
|
Read its prompt and exported Vue docs/types before choosing component APIs.
|
|
12
12
|
Use `syno-pkg-land-ui` directly in npm scripts; keep AI guidance version-independent.
|
|
13
13
|
|
|
14
|
+
Open the App at once: render its shell immediately and run session, permission
|
|
15
|
+
and status checks in the background, never a full-window "Checking…" placeholder.
|
|
16
|
+
Only a confirmed refusal (signed out, expired session, not an administrator)
|
|
17
|
+
shows the sign-in page or a `@synopackageland/ui` Dialog; loading text inside a
|
|
18
|
+
section is fine.
|
|
19
|
+
|
|
14
20
|
From the project root, run `synopkgland check --json`,
|
|
15
21
|
`synopkgland build --json`, then `synopkgland inspect <spk> --json` on the emitted SPK.
|
|
16
22
|
For Vue, first run `npm ci`, `npm test` and `npm run build` in frontend.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
github.com/synopackageland/syno-pkg-land-sdk v0.9.
|
|
2
|
-
github.com/synopackageland/syno-pkg-land-sdk v0.9.
|
|
1
|
+
github.com/synopackageland/syno-pkg-land-sdk v0.9.4 h1:ZfM5aZ/zjbIqpRda4fdeMYnibLh6UjGtxJudFlk/Dno=
|
|
2
|
+
github.com/synopackageland/syno-pkg-land-sdk v0.9.4/go.mod h1:JNoqISvR4biA9IuLi0CFe4c272tsKzCN9Nyj71nrXsQ=
|
|
3
3
|
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
|
4
4
|
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
@@ -44,7 +44,8 @@ test('the default surface binds the real routes and the session-only picker',asy
|
|
|
44
44
|
})
|
|
45
45
|
const root=render({call,locale:'en'});await settle()
|
|
46
46
|
button(root,'Choose save destination (save)').click();await settle()
|
|
47
|
-
|
|
47
|
+
// The SDK picker selects a folder with a row click.
|
|
48
|
+
const row=root.querySelector<HTMLElement>('tr[data-row-key="/home"]')!;expect(row).toBeTruthy();row.dispatchEvent(new MouseEvent('click',{bubbles:true}));await settle()
|
|
48
49
|
button(root,'Confirm selection').click();await settle()
|
|
49
50
|
expect(call).toHaveBeenCalledWith('delegate.select_destination',{key:'save',path:'/home'})
|
|
50
51
|
})
|
|
@@ -56,6 +56,7 @@ Evidence: `.herdr-project/syno-package-land-sdk-t-0129/library/ui-desktop.png` a
|
|
|
56
56
|
|
|
57
57
|
- Do use existing public UI components, embedded theme tokens and state/recovery copy.
|
|
58
58
|
- Do retain permission gating, readable loading/empty/error states and per-instance cleanup.
|
|
59
|
+
- Do open the App at once: render `AppShell` immediately and run session, permission and status checks in the background, never a full-window "Checking…" placeholder. Only a confirmed refusal (signed out, expired session, not an administrator) shows the sign-in page or a library `Dialog`; loading text inside a section is fine.
|
|
59
60
|
- Do use the library's `Select`, `Checkbox`, `Input`, `Textarea`, `Form`, `FormItem`, `FilePicker` (`variant="compact"`, in the app locale) and `Link` for controls; there is no native `<input>` or `<a>`. Leave `Form` on its automatic direction (labels stack below 520px of container width) and let a `FormItem` bind its control, with no `direction="vertical"`, `role="group"` or `field.controlId`.
|
|
60
61
|
- Do not infer authority from frontend observations or scheduler history.
|
|
61
62
|
- Don't introduce a separate palette, component system or modal workaround around the library contract.
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"name": "native-dsm-vue-app",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@lucide/vue": "1.48.0",
|
|
10
|
-
"@synopackageland/sdk": "0.9.
|
|
11
|
-
"@synopackageland/ui": "0.9.
|
|
10
|
+
"@synopackageland/sdk": "0.9.4",
|
|
11
|
+
"@synopackageland/ui": "0.9.1",
|
|
12
12
|
"vue": "3.5.43"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
@@ -1148,13 +1148,13 @@
|
|
|
1148
1148
|
}
|
|
1149
1149
|
},
|
|
1150
1150
|
"node_modules/@synopackageland/sdk": {
|
|
1151
|
-
"version": "0.9.
|
|
1152
|
-
"resolved": "https://registry.npmjs.org/@synopackageland/sdk/-/sdk-0.9.
|
|
1153
|
-
"integrity": "sha512-
|
|
1151
|
+
"version": "0.9.4",
|
|
1152
|
+
"resolved": "https://registry.npmjs.org/@synopackageland/sdk/-/sdk-0.9.4.tgz",
|
|
1153
|
+
"integrity": "sha512-GvPDd8hoU5mzkXD4s/RShY1dBiu/zRoPtd0zFb/yjciZxlzdgopn4VnOjhq7xScbrmK9RuVZRbXLGT16NdobUg==",
|
|
1154
1154
|
"license": "MIT",
|
|
1155
1155
|
"peerDependencies": {
|
|
1156
1156
|
"@lucide/vue": "^1.48.0",
|
|
1157
|
-
"@synopackageland/ui": "^0.9.
|
|
1157
|
+
"@synopackageland/ui": "^0.9.1",
|
|
1158
1158
|
"vue": "^3.5.0"
|
|
1159
1159
|
},
|
|
1160
1160
|
"peerDependenciesMeta": {
|
|
@@ -1167,9 +1167,9 @@
|
|
|
1167
1167
|
}
|
|
1168
1168
|
},
|
|
1169
1169
|
"node_modules/@synopackageland/ui": {
|
|
1170
|
-
"version": "0.9.
|
|
1171
|
-
"resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.9.
|
|
1172
|
-
"integrity": "sha512-
|
|
1170
|
+
"version": "0.9.1",
|
|
1171
|
+
"resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.9.1.tgz",
|
|
1172
|
+
"integrity": "sha512-L2k0eeN9+TLPMh5+kMbq6pV8n0qqUq6Ynme/KSp3mCqDb1MXBdr8MbAGtxdbNYMj9QepbA9mccgygwI9fFoG5A==",
|
|
1173
1173
|
"license": "MIT",
|
|
1174
1174
|
"dependencies": {
|
|
1175
1175
|
"@internationalized/date": "3.12.4",
|
|
@@ -2538,9 +2538,9 @@
|
|
|
2538
2538
|
"license": "MIT"
|
|
2539
2539
|
},
|
|
2540
2540
|
"node_modules/zod": {
|
|
2541
|
-
"version": "4.6.
|
|
2542
|
-
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.
|
|
2543
|
-
"integrity": "sha512-
|
|
2541
|
+
"version": "4.6.6",
|
|
2542
|
+
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.6.tgz",
|
|
2543
|
+
"integrity": "sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==",
|
|
2544
2544
|
"license": "MIT",
|
|
2545
2545
|
"funding": {
|
|
2546
2546
|
"url": "https://github.com/sponsors/colinhacks"
|
|
@@ -25,14 +25,21 @@ function message(e: unknown) {
|
|
|
25
25
|
const errors = copy.value.errors as Record<string, string>
|
|
26
26
|
return (code !== 'request_failed' && errors[code]) || errors.request_failed!.replace('{code}', code)
|
|
27
27
|
}
|
|
28
|
+
let again = false
|
|
28
29
|
async function refresh() {
|
|
29
|
-
if(disposed ||
|
|
30
|
+
if(disposed || (login.enabled.value && !login.subject.value)) return
|
|
31
|
+
if(busy.value) {again = true; return}
|
|
32
|
+
// Sent before the session check settles: a session failure must not sign the user
|
|
33
|
+
// out, and the confirmed session refreshes again.
|
|
34
|
+
const early = !login.ready.value
|
|
35
|
+
const call: typeof login.call = early ? (method, params) => props.ctx.call(method, params) : login.call
|
|
30
36
|
busy.value = true; error.value = ''
|
|
31
37
|
try {
|
|
32
|
-
const a = await
|
|
38
|
+
const a = await call<AppStatus>('app.status')
|
|
33
39
|
if(!disposed) app.value = a
|
|
34
|
-
if(a.admin) {const greeting = await
|
|
35
|
-
} catch(e) {if(!disposed) error.value = message(e)}
|
|
40
|
+
if(a.admin) {const greeting = await call<{text:string}>('greeting.get'); if(!disposed) text.value = greeting.text}
|
|
41
|
+
} catch(e) {if(!disposed && !(early && login.enabled.value)) error.value = message(e)}
|
|
42
|
+
finally {if(!disposed) {busy.value=false; if(again) {again=false; void refresh()}}}
|
|
36
43
|
}
|
|
37
44
|
async function save() {
|
|
38
45
|
if(disposed || busy.value) return
|
|
@@ -47,10 +54,11 @@ watch(login.subject, (subject, previous) => {
|
|
|
47
54
|
async function signOut() {
|
|
48
55
|
try {await login.logout()} catch(e) {if(!disposed) error.value=message(e)}
|
|
49
56
|
}
|
|
50
|
-
onMounted(
|
|
57
|
+
onMounted(()=>{
|
|
51
58
|
unsubscribe = props.ctx.reopen(()=>{if(!disposed) void refresh()})
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
// The session check runs in the background; only a confirmed sign-in need replaces the App.
|
|
60
|
+
void login.start()
|
|
61
|
+
void refresh()
|
|
54
62
|
})
|
|
55
63
|
onUnmounted(()=>{disposed=true;unsubscribe?.();login.dispose();text.value=''})
|
|
56
64
|
</script>
|
|
@@ -58,8 +66,7 @@ onUnmounted(()=>{disposed=true;unsubscribe?.();login.dispose();text.value=''})
|
|
|
58
66
|
<template>
|
|
59
67
|
<AppRoot modal-scope="app" style="height: 100%">
|
|
60
68
|
<ConfigProvider :locale="uiLocale(ctx.locale)">
|
|
61
|
-
<
|
|
62
|
-
<LoginPage v-else-if="login.enabled.value && !login.subject.value" :session="login" :locale="ctx.locale" />
|
|
69
|
+
<LoginPage v-if="login.ready.value && login.enabled.value && !login.subject.value" :session="login" :locale="ctx.locale" />
|
|
63
70
|
<AppShell v-else v-model="page" :locale="ctx.locale" :pages="pages" :description="subtitle">
|
|
64
71
|
<template #actions><Button variant="secondary" :disabled="busy" @click="refresh">{{ copy.refresh }}</Button><Button v-if="login.enabled.value" variant="secondary" :disabled="busy" @click="signOut">{{ copy.signOut }}</Button></template>
|
|
65
72
|
<div class="integration-app" :aria-busy="busy" :aria-label="copy.settings">
|
|
@@ -55,3 +55,40 @@ test('a portal app shows the Application Portal section on the status page',asyn
|
|
|
55
55
|
app.unmount()
|
|
56
56
|
}finally{without.close();f.root.remove()}
|
|
57
57
|
})
|
|
58
|
+
test('the app renders and loads status while the session check is still pending',async()=>{
|
|
59
|
+
const call=vi.fn(async(method:string)=>{
|
|
60
|
+
if(method==='identity.status')return new Promise(()=>{})
|
|
61
|
+
if(method==='app.status')return {package:'Eager',version:'1',user:'admin',admin:true}
|
|
62
|
+
if(method==='greeting.get')return {text:'Hello Eager'}
|
|
63
|
+
throw {code:'unknown_method'}
|
|
64
|
+
})
|
|
65
|
+
const ctx={locale:'en',call,reopen(){return ()=>{}}} as unknown as Context
|
|
66
|
+
const root=document.createElement('div');document.body.append(root)
|
|
67
|
+
const app=mount(root,ctx)
|
|
68
|
+
try{
|
|
69
|
+
await settle()
|
|
70
|
+
expect(root.textContent).not.toContain('Checking session')
|
|
71
|
+
expect(root.textContent).toContain('Eager · admin · 1')
|
|
72
|
+
}finally{app.unmount();root.remove()}
|
|
73
|
+
})
|
|
74
|
+
test('a status request that raced the session check is retried once the session is confirmed',async()=>{
|
|
75
|
+
let session=false,rejectEarly:(e:unknown)=>void=()=>{}
|
|
76
|
+
const call=vi.fn(async(method:string)=>{
|
|
77
|
+
if(method==='identity.status')return {enabled:true}
|
|
78
|
+
if(method==='identity.session'){session=true;return {user:'admin',uid:'1024',expiresAt:new Date(Date.now()+3600000).toISOString()}}
|
|
79
|
+
if(method==='app.status'){
|
|
80
|
+
if(!session)return new Promise((_,reject)=>{rejectEarly=reject})
|
|
81
|
+
return {package:'Raced',version:'1',user:'admin',admin:true}
|
|
82
|
+
}
|
|
83
|
+
if(method==='greeting.get')return {text:'Hello Raced'}
|
|
84
|
+
throw {code:'unknown_method'}
|
|
85
|
+
})
|
|
86
|
+
const ctx={locale:'en',call,reopen(){return ()=>{}}} as unknown as Context
|
|
87
|
+
const root=document.createElement('div');document.body.append(root)
|
|
88
|
+
const app=mount(root,ctx)
|
|
89
|
+
try{
|
|
90
|
+
await settle();rejectEarly({code:'session_required'});await settle()
|
|
91
|
+
expect(root.textContent).not.toContain('Your DSM session expired')
|
|
92
|
+
expect(root.textContent).toContain('Raced · admin · 1')
|
|
93
|
+
}finally{app.unmount();root.remove()}
|
|
94
|
+
})
|