@puredesktop/create-app 2.1.9 → 2.1.10
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/bin/create-puredesktop-app.mjs +70 -338
- package/package.json +3 -2
- package/template/README.md +3 -3
- package/template/docs/README.md +9 -8
- package/template/docs/howtos/persisting-app-state.md +242 -242
- package/template/docs/howtos/using-document-editor.md +440 -0
- package/template/docs/plugin-manifest.md +1 -1
- package/template/docs/ui-and-components.md +84 -79
- package/template/gitignore.tpl +6 -1
- package/template/package.json +1 -1
- package/template/plugin.json +1 -1
- package/template/scripts/validate-puredesktop-app.mjs +46 -46
- package/template/src/App.tsx +8 -8
- package/template/src/components/AppShell.tsx +11 -11
- package/template/src/components/StarterWorkspace.tsx +154 -154
- package/template/src/constants.ts +2 -2
- package/template/src/lib/starterWorkspace.ts +29 -29
- package/template/vite.config.js +13 -13
|
@@ -20,10 +20,10 @@ if (packageJson) {
|
|
|
20
20
|
validatePackageJson(packageJson)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
if (manifest) {
|
|
24
|
-
validateManifest(manifest)
|
|
25
|
-
await validateBridgePermissionUsage(manifest)
|
|
26
|
-
}
|
|
23
|
+
if (manifest) {
|
|
24
|
+
validateManifest(manifest)
|
|
25
|
+
await validateBridgePermissionUsage(manifest)
|
|
26
|
+
}
|
|
27
27
|
|
|
28
28
|
if (await isDirectory(distPath)) {
|
|
29
29
|
info.push('dist folder is present for static registration.')
|
|
@@ -136,7 +136,7 @@ function validateManifest(manifest) {
|
|
|
136
136
|
if (errors.length === 0) info.push('plugin.json is valid.')
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
function validateEntrypoint(entrypoint, permissions) {
|
|
139
|
+
function validateEntrypoint(entrypoint, permissions) {
|
|
140
140
|
if (!isPlainObject(entrypoint)) {
|
|
141
141
|
errors.push('entrypoint is required.')
|
|
142
142
|
return
|
|
@@ -170,47 +170,47 @@ function validateEntrypoint(entrypoint, permissions) {
|
|
|
170
170
|
}
|
|
171
171
|
return
|
|
172
172
|
}
|
|
173
|
-
errors.push(`Unsupported entrypoint kind: ${String(entrypoint.kind ?? '') || '(missing)'}.`)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async function validateBridgePermissionUsage(manifest) {
|
|
177
|
-
const permissions = Array.isArray(manifest.permissions) ? manifest.permissions : []
|
|
178
|
-
const sourceText = await readProjectText(join(ROOT, 'src'))
|
|
179
|
-
const requirements = [
|
|
180
|
-
{
|
|
181
|
-
permission: 'filesystem',
|
|
182
|
-
hints: [
|
|
183
|
-
'/bridge/storage',
|
|
184
|
-
'/bridge/fs',
|
|
185
|
-
'/bridge/dialog',
|
|
186
|
-
'/bridge/react/usePlatformJsonStore',
|
|
187
|
-
],
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
permission: 'settings',
|
|
191
|
-
hints: [
|
|
192
|
-
'/bridge/appSettings',
|
|
193
|
-
'/bridge/preferences',
|
|
194
|
-
'/bridge/react/usePlatformAppSettings',
|
|
195
|
-
'/bridge/react/usePlatformPreferences',
|
|
196
|
-
],
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
permission: 'network',
|
|
200
|
-
hints: ['/bridge/network', '/bridge/vision'],
|
|
201
|
-
},
|
|
202
|
-
]
|
|
203
|
-
|
|
204
|
-
for (const requirement of requirements) {
|
|
205
|
-
if (permissions.includes(requirement.permission)) continue
|
|
206
|
-
if (!requirement.hints.some(hint => sourceText.includes(hint))) continue
|
|
207
|
-
errors.push(
|
|
208
|
-
`Source imports ${requirement.permission}-gated bridge helpers, so plugin.json must include the ${requirement.permission} permission.`,
|
|
209
|
-
)
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
async function validateAgentTools(manifest, distPath) {
|
|
173
|
+
errors.push(`Unsupported entrypoint kind: ${String(entrypoint.kind ?? '') || '(missing)'}.`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function validateBridgePermissionUsage(manifest) {
|
|
177
|
+
const permissions = Array.isArray(manifest.permissions) ? manifest.permissions : []
|
|
178
|
+
const sourceText = await readProjectText(join(ROOT, 'src'))
|
|
179
|
+
const requirements = [
|
|
180
|
+
{
|
|
181
|
+
permission: 'filesystem',
|
|
182
|
+
hints: [
|
|
183
|
+
'/bridge/storage',
|
|
184
|
+
'/bridge/fs',
|
|
185
|
+
'/bridge/dialog',
|
|
186
|
+
'/bridge/react/usePlatformJsonStore',
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
permission: 'settings',
|
|
191
|
+
hints: [
|
|
192
|
+
'/bridge/appSettings',
|
|
193
|
+
'/bridge/preferences',
|
|
194
|
+
'/bridge/react/usePlatformAppSettings',
|
|
195
|
+
'/bridge/react/usePlatformPreferences',
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
permission: 'network',
|
|
200
|
+
hints: ['/bridge/network', '/bridge/vision'],
|
|
201
|
+
},
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
for (const requirement of requirements) {
|
|
205
|
+
if (permissions.includes(requirement.permission)) continue
|
|
206
|
+
if (!requirement.hints.some(hint => sourceText.includes(hint))) continue
|
|
207
|
+
errors.push(
|
|
208
|
+
`Source imports ${requirement.permission}-gated bridge helpers, so plugin.json must include the ${requirement.permission} permission.`,
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function validateAgentTools(manifest, distPath) {
|
|
214
214
|
const tools = manifest.app?.agents?.tools ?? []
|
|
215
215
|
if (!Array.isArray(tools) || tools.length === 0) {
|
|
216
216
|
info.push('No app agent tools declared.')
|
package/template/src/App.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AppFrame } from '@puredesktop/puredesktop-ui-bridge/components/common/containers/AppFrame'
|
|
2
2
|
import { EmptyState } from '@puredesktop/puredesktop-ui-bridge/components/common/feedback/EmptyState'
|
|
3
3
|
import { usePlatformBridge } from '@puredesktop/puredesktop-ui-bridge/bridge/react/usePlatformBridge'
|
|
4
|
-
import { AppShell } from './components/AppShell'
|
|
5
|
-
import { isStandaloneDevMode } from './bridge/platformBridge'
|
|
6
|
-
import { APP_TITLE } from './constants'
|
|
7
|
-
import { useAppBoot } from './hooks/useAppBoot'
|
|
4
|
+
import { AppShell } from './components/AppShell'
|
|
5
|
+
import { isStandaloneDevMode } from './bridge/platformBridge'
|
|
6
|
+
import { APP_TITLE } from './constants'
|
|
7
|
+
import { useAppBoot } from './hooks/useAppBoot'
|
|
8
8
|
|
|
9
9
|
export function App(): React.ReactElement {
|
|
10
10
|
const { error: bridgeError, ready } = usePlatformBridge()
|
|
@@ -14,7 +14,7 @@ export function App(): React.ReactElement {
|
|
|
14
14
|
|
|
15
15
|
if (bridgeError && !standaloneDev) {
|
|
16
16
|
return (
|
|
17
|
-
<AppFrame>
|
|
17
|
+
<AppFrame hideWordmark>
|
|
18
18
|
<EmptyState
|
|
19
19
|
tone="error"
|
|
20
20
|
title="Bridge unavailable"
|
|
@@ -26,10 +26,10 @@ export function App(): React.ReactElement {
|
|
|
26
26
|
|
|
27
27
|
if (!bootReady || !boot) {
|
|
28
28
|
return (
|
|
29
|
-
<AppFrame>
|
|
29
|
+
<AppFrame hideWordmark>
|
|
30
30
|
<EmptyState
|
|
31
31
|
tone={bootError ? 'error' : 'neutral'}
|
|
32
|
-
title={bootError ? 'Boot failed' : APP_TITLE}
|
|
32
|
+
title={bootError ? 'Boot failed' : APP_TITLE}
|
|
33
33
|
message={
|
|
34
34
|
bootError
|
|
35
35
|
? bootError.message
|
|
@@ -43,7 +43,7 @@ export function App(): React.ReactElement {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
|
-
<AppFrame>
|
|
46
|
+
<AppFrame hideWordmark>
|
|
47
47
|
<AppShell settings={boot.settings} />
|
|
48
48
|
</AppFrame>
|
|
49
49
|
)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { AppSettings } from '../types'
|
|
2
|
-
import { APP_TITLE } from '../constants'
|
|
3
|
-
import { StarterWorkspace } from './StarterWorkspace'
|
|
4
|
-
|
|
5
|
-
export interface AppShellProps {
|
|
6
|
-
settings: AppSettings
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function AppShell({ settings }: AppShellProps): React.ReactElement {
|
|
10
|
-
return <StarterWorkspace appName={APP_TITLE} settings={settings} />
|
|
11
|
-
}
|
|
1
|
+
import type { AppSettings } from '../types'
|
|
2
|
+
import { APP_TITLE } from '../constants'
|
|
3
|
+
import { StarterWorkspace } from './StarterWorkspace'
|
|
4
|
+
|
|
5
|
+
export interface AppShellProps {
|
|
6
|
+
settings: AppSettings
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function AppShell({ settings }: AppShellProps): React.ReactElement {
|
|
10
|
+
return <StarterWorkspace appName={APP_TITLE} settings={settings} />
|
|
11
|
+
}
|
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
import styled from 'styled-components'
|
|
2
|
-
import { Heading } from '@puredesktop/puredesktop-ui-bridge/components/common/typography/Heading'
|
|
3
|
-
import { Text } from '@puredesktop/puredesktop-ui-bridge/components/common/typography/Text'
|
|
4
|
-
import {
|
|
5
|
-
readConfiguredSettingCount,
|
|
6
|
-
starterMilestones,
|
|
7
|
-
} from '../lib/starterWorkspace'
|
|
8
|
-
import type { AppSettings } from '../types'
|
|
9
|
-
|
|
10
|
-
const Root = styled.div`
|
|
11
|
-
display: flex;
|
|
12
|
-
min-height: 100%;
|
|
13
|
-
width: 100%;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
padding: var(--platform-spacing-xxl);
|
|
17
|
-
color: var(--platform-colors-text);
|
|
18
|
-
background:
|
|
19
|
-
linear-gradient(
|
|
20
|
-
135deg,
|
|
21
|
-
color-mix(in srgb, var(--platform-colors-accent) 6%, transparent),
|
|
22
|
-
transparent 42%
|
|
23
|
-
),
|
|
24
|
-
var(--platform-colors-bg);
|
|
25
|
-
`
|
|
26
|
-
|
|
27
|
-
const Panel = styled.section`
|
|
28
|
-
display: grid;
|
|
29
|
-
width: min(760px, 100%);
|
|
30
|
-
gap: var(--platform-spacing-xl);
|
|
31
|
-
`
|
|
32
|
-
|
|
33
|
-
const Header = styled.header`
|
|
34
|
-
display: grid;
|
|
35
|
-
gap: var(--platform-spacing-sm);
|
|
36
|
-
`
|
|
37
|
-
|
|
38
|
-
const MilestoneGrid = styled.div`
|
|
39
|
-
display: grid;
|
|
40
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
41
|
-
gap: var(--platform-spacing-md);
|
|
42
|
-
|
|
43
|
-
@media (max-width: 720px) {
|
|
44
|
-
grid-template-columns: 1fr;
|
|
45
|
-
}
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
const Milestone = styled.article`
|
|
49
|
-
display: grid;
|
|
50
|
-
gap: var(--platform-spacing-sm);
|
|
51
|
-
min-width: 0;
|
|
52
|
-
padding: var(--platform-spacing-lg);
|
|
53
|
-
border: 1px solid var(--platform-colors-border);
|
|
54
|
-
background: var(--platform-colors-surface);
|
|
55
|
-
box-shadow: var(--platform-shadow-sm);
|
|
56
|
-
`
|
|
57
|
-
|
|
58
|
-
const PulseTrack = styled.div`
|
|
59
|
-
position: relative;
|
|
60
|
-
overflow: hidden;
|
|
61
|
-
height: 3px;
|
|
62
|
-
background: var(--platform-colors-border);
|
|
63
|
-
`
|
|
64
|
-
|
|
65
|
-
const Pulse = styled.div`
|
|
66
|
-
position: absolute;
|
|
67
|
-
inset: 0 auto 0 0;
|
|
68
|
-
width: 44%;
|
|
69
|
-
background: var(--platform-colors-accent);
|
|
70
|
-
animation: starter-pulse 1600ms ease-in-out infinite;
|
|
71
|
-
|
|
72
|
-
@keyframes starter-pulse {
|
|
73
|
-
0% {
|
|
74
|
-
transform: translateX(-100%);
|
|
75
|
-
opacity: 0.35;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
45%,
|
|
79
|
-
60% {
|
|
80
|
-
opacity: 1;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
100% {
|
|
84
|
-
transform: translateX(230%);
|
|
85
|
-
opacity: 0.35;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
const MetaRow = styled.div`
|
|
91
|
-
display: flex;
|
|
92
|
-
flex-wrap: wrap;
|
|
93
|
-
gap: var(--platform-spacing-sm);
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
const MetaPill = styled.span`
|
|
97
|
-
padding: 4px 8px;
|
|
98
|
-
border: 1px solid var(--platform-colors-border);
|
|
99
|
-
background: var(--platform-colors-surface);
|
|
100
|
-
color: var(--platform-colors-text-secondary);
|
|
101
|
-
font-size: var(--platform-typography-font-size-xs);
|
|
102
|
-
font-weight: var(--platform-typography-font-weight-medium);
|
|
103
|
-
`
|
|
104
|
-
|
|
105
|
-
interface StarterWorkspaceProps {
|
|
106
|
-
appName: string
|
|
107
|
-
settings: AppSettings
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function StarterWorkspace({
|
|
111
|
-
appName,
|
|
112
|
-
settings,
|
|
113
|
-
}: StarterWorkspaceProps): React.ReactElement {
|
|
114
|
-
const settingCount = readConfiguredSettingCount(settings)
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<Root>
|
|
118
|
-
<Panel aria-label={`${appName} starter workspace`}>
|
|
119
|
-
<Header>
|
|
120
|
-
<Text size="sm" tone="secondary">
|
|
121
|
-
PureDesktop app workspace
|
|
122
|
-
</Text>
|
|
123
|
-
<Heading level={1}>{appName}</Heading>
|
|
124
|
-
<Text tone="secondary">
|
|
125
|
-
This workspace is ready. The first build step will replace this
|
|
126
|
-
starter screen with the product experience.
|
|
127
|
-
</Text>
|
|
128
|
-
</Header>
|
|
129
|
-
|
|
130
|
-
<MilestoneGrid>
|
|
131
|
-
{starterMilestones.map(milestone => (
|
|
132
|
-
<Milestone key={milestone.id}>
|
|
133
|
-
<Text size="sm" weight="medium">
|
|
134
|
-
{milestone.title}
|
|
135
|
-
</Text>
|
|
136
|
-
<Text size="sm" tone="secondary">
|
|
137
|
-
{milestone.detail}
|
|
138
|
-
</Text>
|
|
139
|
-
<PulseTrack aria-hidden="true">
|
|
140
|
-
<Pulse />
|
|
141
|
-
</PulseTrack>
|
|
142
|
-
</Milestone>
|
|
143
|
-
))}
|
|
144
|
-
</MilestoneGrid>
|
|
145
|
-
|
|
146
|
-
<MetaRow>
|
|
147
|
-
<MetaPill>{settingCount} setting key(s)</MetaPill>
|
|
148
|
-
<MetaPill>Theme active</MetaPill>
|
|
149
|
-
<MetaPill>Shell connected</MetaPill>
|
|
150
|
-
</MetaRow>
|
|
151
|
-
</Panel>
|
|
152
|
-
</Root>
|
|
153
|
-
)
|
|
154
|
-
}
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import { Heading } from '@puredesktop/puredesktop-ui-bridge/components/common/typography/Heading'
|
|
3
|
+
import { Text } from '@puredesktop/puredesktop-ui-bridge/components/common/typography/Text'
|
|
4
|
+
import {
|
|
5
|
+
readConfiguredSettingCount,
|
|
6
|
+
starterMilestones,
|
|
7
|
+
} from '../lib/starterWorkspace'
|
|
8
|
+
import type { AppSettings } from '../types'
|
|
9
|
+
|
|
10
|
+
const Root = styled.div`
|
|
11
|
+
display: flex;
|
|
12
|
+
min-height: 100%;
|
|
13
|
+
width: 100%;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
padding: var(--platform-spacing-xxl);
|
|
17
|
+
color: var(--platform-colors-text);
|
|
18
|
+
background:
|
|
19
|
+
linear-gradient(
|
|
20
|
+
135deg,
|
|
21
|
+
color-mix(in srgb, var(--platform-colors-accent) 6%, transparent),
|
|
22
|
+
transparent 42%
|
|
23
|
+
),
|
|
24
|
+
var(--platform-colors-bg);
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
const Panel = styled.section`
|
|
28
|
+
display: grid;
|
|
29
|
+
width: min(760px, 100%);
|
|
30
|
+
gap: var(--platform-spacing-xl);
|
|
31
|
+
`
|
|
32
|
+
|
|
33
|
+
const Header = styled.header`
|
|
34
|
+
display: grid;
|
|
35
|
+
gap: var(--platform-spacing-sm);
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
const MilestoneGrid = styled.div`
|
|
39
|
+
display: grid;
|
|
40
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
41
|
+
gap: var(--platform-spacing-md);
|
|
42
|
+
|
|
43
|
+
@media (max-width: 720px) {
|
|
44
|
+
grid-template-columns: 1fr;
|
|
45
|
+
}
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
const Milestone = styled.article`
|
|
49
|
+
display: grid;
|
|
50
|
+
gap: var(--platform-spacing-sm);
|
|
51
|
+
min-width: 0;
|
|
52
|
+
padding: var(--platform-spacing-lg);
|
|
53
|
+
border: 1px solid var(--platform-colors-border);
|
|
54
|
+
background: var(--platform-colors-surface);
|
|
55
|
+
box-shadow: var(--platform-shadow-sm);
|
|
56
|
+
`
|
|
57
|
+
|
|
58
|
+
const PulseTrack = styled.div`
|
|
59
|
+
position: relative;
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
height: 3px;
|
|
62
|
+
background: var(--platform-colors-border);
|
|
63
|
+
`
|
|
64
|
+
|
|
65
|
+
const Pulse = styled.div`
|
|
66
|
+
position: absolute;
|
|
67
|
+
inset: 0 auto 0 0;
|
|
68
|
+
width: 44%;
|
|
69
|
+
background: var(--platform-colors-accent);
|
|
70
|
+
animation: starter-pulse 1600ms ease-in-out infinite;
|
|
71
|
+
|
|
72
|
+
@keyframes starter-pulse {
|
|
73
|
+
0% {
|
|
74
|
+
transform: translateX(-100%);
|
|
75
|
+
opacity: 0.35;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
45%,
|
|
79
|
+
60% {
|
|
80
|
+
opacity: 1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
100% {
|
|
84
|
+
transform: translateX(230%);
|
|
85
|
+
opacity: 0.35;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
`
|
|
89
|
+
|
|
90
|
+
const MetaRow = styled.div`
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-wrap: wrap;
|
|
93
|
+
gap: var(--platform-spacing-sm);
|
|
94
|
+
`
|
|
95
|
+
|
|
96
|
+
const MetaPill = styled.span`
|
|
97
|
+
padding: 4px 8px;
|
|
98
|
+
border: 1px solid var(--platform-colors-border);
|
|
99
|
+
background: var(--platform-colors-surface);
|
|
100
|
+
color: var(--platform-colors-text-secondary);
|
|
101
|
+
font-size: var(--platform-typography-font-size-xs);
|
|
102
|
+
font-weight: var(--platform-typography-font-weight-medium);
|
|
103
|
+
`
|
|
104
|
+
|
|
105
|
+
interface StarterWorkspaceProps {
|
|
106
|
+
appName: string
|
|
107
|
+
settings: AppSettings
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function StarterWorkspace({
|
|
111
|
+
appName,
|
|
112
|
+
settings,
|
|
113
|
+
}: StarterWorkspaceProps): React.ReactElement {
|
|
114
|
+
const settingCount = readConfiguredSettingCount(settings)
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<Root>
|
|
118
|
+
<Panel aria-label={`${appName} starter workspace`}>
|
|
119
|
+
<Header>
|
|
120
|
+
<Text size="sm" tone="secondary">
|
|
121
|
+
PureDesktop app workspace
|
|
122
|
+
</Text>
|
|
123
|
+
<Heading level={1}>{appName}</Heading>
|
|
124
|
+
<Text tone="secondary">
|
|
125
|
+
This workspace is ready. The first build step will replace this
|
|
126
|
+
starter screen with the product experience.
|
|
127
|
+
</Text>
|
|
128
|
+
</Header>
|
|
129
|
+
|
|
130
|
+
<MilestoneGrid>
|
|
131
|
+
{starterMilestones.map(milestone => (
|
|
132
|
+
<Milestone key={milestone.id}>
|
|
133
|
+
<Text size="sm" weight="medium">
|
|
134
|
+
{milestone.title}
|
|
135
|
+
</Text>
|
|
136
|
+
<Text size="sm" tone="secondary">
|
|
137
|
+
{milestone.detail}
|
|
138
|
+
</Text>
|
|
139
|
+
<PulseTrack aria-hidden="true">
|
|
140
|
+
<Pulse />
|
|
141
|
+
</PulseTrack>
|
|
142
|
+
</Milestone>
|
|
143
|
+
))}
|
|
144
|
+
</MilestoneGrid>
|
|
145
|
+
|
|
146
|
+
<MetaRow>
|
|
147
|
+
<MetaPill>{settingCount} setting key(s)</MetaPill>
|
|
148
|
+
<MetaPill>Theme active</MetaPill>
|
|
149
|
+
<MetaPill>Shell connected</MetaPill>
|
|
150
|
+
</MetaRow>
|
|
151
|
+
</Panel>
|
|
152
|
+
</Root>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const APP_SLUG = '{{APP_SLUG}}'
|
|
2
|
-
export const APP_TITLE = '{{APP_TITLE}}'
|
|
1
|
+
export const APP_SLUG = '{{APP_SLUG}}'
|
|
2
|
+
export const APP_TITLE = '{{APP_TITLE}}'
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import type { AppSettings } from '../types'
|
|
2
|
-
|
|
3
|
-
export interface StarterMilestone {
|
|
4
|
-
detail: string
|
|
5
|
-
id: string
|
|
6
|
-
title: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const starterMilestones: StarterMilestone[] = [
|
|
10
|
-
{
|
|
11
|
-
detail: 'The starter screen is temporary and ready to be replaced by product UI.',
|
|
12
|
-
id: 'boot',
|
|
13
|
-
title: 'Workspace ready',
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
detail: 'PureDesktop styling is loaded so early screens match the shell.',
|
|
17
|
-
id: 'composition',
|
|
18
|
-
title: 'Theme active',
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
detail: 'Settings and shell capabilities are available through the app bridge.',
|
|
22
|
-
id: 'domain',
|
|
23
|
-
title: 'Shell connected',
|
|
24
|
-
},
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
export function readConfiguredSettingCount(settings: AppSettings): number {
|
|
28
|
-
return Object.keys(settings).length
|
|
29
|
-
}
|
|
1
|
+
import type { AppSettings } from '../types'
|
|
2
|
+
|
|
3
|
+
export interface StarterMilestone {
|
|
4
|
+
detail: string
|
|
5
|
+
id: string
|
|
6
|
+
title: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const starterMilestones: StarterMilestone[] = [
|
|
10
|
+
{
|
|
11
|
+
detail: 'The starter screen is temporary and ready to be replaced by product UI.',
|
|
12
|
+
id: 'boot',
|
|
13
|
+
title: 'Workspace ready',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
detail: 'PureDesktop styling is loaded so early screens match the shell.',
|
|
17
|
+
id: 'composition',
|
|
18
|
+
title: 'Theme active',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
detail: 'Settings and shell capabilities are available through the app bridge.',
|
|
22
|
+
id: 'domain',
|
|
23
|
+
title: 'Shell connected',
|
|
24
|
+
},
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
export function readConfiguredSettingCount(settings: AppSettings): number {
|
|
28
|
+
return Object.keys(settings).length
|
|
29
|
+
}
|
package/template/vite.config.js
CHANGED
|
@@ -16,16 +16,16 @@ function devServerFromManifest(metaUrl) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export default defineConfig({
|
|
20
|
-
plugins: [
|
|
21
|
-
react({
|
|
22
|
-
plugins: [
|
|
23
|
-
['@swc/plugin-styled-components', { displayName: true, fileName: true }],
|
|
24
|
-
],
|
|
25
|
-
}),
|
|
26
|
-
],
|
|
27
|
-
optimizeDeps: {
|
|
28
|
-
include: ['react-dom'],
|
|
29
|
-
},
|
|
30
|
-
server: devServerFromManifest(import.meta.url),
|
|
31
|
-
})
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
plugins: [
|
|
21
|
+
react({
|
|
22
|
+
plugins: [
|
|
23
|
+
['@swc/plugin-styled-components', { displayName: true, fileName: true }],
|
|
24
|
+
],
|
|
25
|
+
}),
|
|
26
|
+
],
|
|
27
|
+
optimizeDeps: {
|
|
28
|
+
include: ['react-dom'],
|
|
29
|
+
},
|
|
30
|
+
server: devServerFromManifest(import.meta.url),
|
|
31
|
+
})
|