@sempods/apps 0.0.0-stage → 0.7.0-dev.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/LICENSE +19 -0
  3. package/README.md +50 -2
  4. package/bin/sempods-apps.mjs +36 -0
  5. package/instructions/app-workflow.md +210 -0
  6. package/instructions/publish.md +148 -0
  7. package/instructions/setup.md +112 -0
  8. package/package.json +59 -3
  9. package/scripts/build-site.mjs +181 -0
  10. package/scripts/check.mjs +260 -0
  11. package/scripts/configure-site.mjs +118 -0
  12. package/scripts/create.mjs +83 -0
  13. package/scripts/dev.mjs +19 -0
  14. package/scripts/lib/apps.mjs +164 -0
  15. package/scripts/lib/files.mjs +57 -0
  16. package/scripts/lib/generate.mjs +175 -0
  17. package/scripts/lib/html.mjs +3 -0
  18. package/scripts/lib/json.mjs +42 -0
  19. package/scripts/lib/merge.mjs +159 -0
  20. package/scripts/lib/overview.mjs +105 -0
  21. package/scripts/lib/paths.mjs +33 -0
  22. package/scripts/lib/pnpm.mjs +30 -0
  23. package/scripts/lib/sdk.mjs +45 -0
  24. package/scripts/lib/shared.mjs +221 -0
  25. package/scripts/migrate.mjs +550 -0
  26. package/scripts/new-app.mjs +166 -0
  27. package/scripts/regenerate.mjs +41 -0
  28. package/scripts/sdk-update.mjs +220 -0
  29. package/scripts/update.mjs +152 -0
  30. package/shared/files/0221b70340152ba3cc615342254603b29b89c4d7d944d7b3b3e647873bdcefa0 +11 -0
  31. package/shared/files/1c29eed0a0297c742ca5814de9081ff8efce3b2da5d9e1bdf90a403873da4aa9 +61 -0
  32. package/shared/files/31ede0d1a035d8531100ebfd751510c772ab0871309a0b9752a77fb96187eb53 +25 -0
  33. package/shared/files/393b09ecd18889d9e1fdf2a6df12d1f6c193fc7fcd89ba808ad94df521bad8db +41 -0
  34. package/shared/files/4660ca1887261ae658c7f587190e2b71ac91d15b1b6c4cf04dec823fdd0da654 +22 -0
  35. package/shared/files/528d7544990228d6d66fa62423775338fe1716326fc68371fb2665aee86e39b0 +6 -0
  36. package/shared/files/676e6bf33b44044fb4c62c9fe5441729bd47f2d5077ff904e57a4a1b658859ac +38 -0
  37. package/shared/files/8b7567319e18694aeca8722107281a760e3100239c6d0f548acb71e1806fa172 +28 -0
  38. package/shared/files/9945f9bd573e5af8aad2e65cc8b4284fbb1d7e8b96c4efdd42eeb49132fb4327 +4 -0
  39. package/shared/files/b911698dc3cf3227d34d0ad6727f1c4408791203c9665de99ab959331a1fb07d +1 -0
  40. package/shared/files/c2a4b026a87f6c8e7473e7fc456c6020b63c7e675087f6b58013ed5163c2f6ee +23 -0
  41. package/shared/files/d3ca3a13c6524a2ee9382977ddc0539790813225f366306024a58c957a4716ee +11 -0
  42. package/shared/files/e13474647ebaf26d042d76f4917203d209a2b14c0d2af8d12139ea328530823e +78 -0
  43. package/shared/files/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +0 -0
  44. package/shared/files/f2fb601d5ebc4ca72274ac9d3fcf7b7a96e27958bb92cd065a9165b501ee94d7 +57 -0
  45. package/shared/files/f9e2544290ee8418484e47194444ee58c2ea675682871ae8a8727d1edb17978e +5 -0
  46. package/shared/files/ff9f774d9732fdc7d704ef37ddc40ef507a1bab7ee3c1db5b1a6a8f26e6e4b37 +35 -0
  47. package/shared/snapshot.json +25 -0
  48. package/skeleton/app/.oxlintrc.json +14 -0
  49. package/skeleton/app/NOTES.md +19 -0
  50. package/skeleton/app/index.html +16 -0
  51. package/skeleton/app/package.json +28 -0
  52. package/skeleton/app/public/favicon.svg +1 -0
  53. package/skeleton/app/public/icon-192.png +0 -0
  54. package/skeleton/app/public/icon-512.png +0 -0
  55. package/skeleton/app/src/App.tsx +73 -0
  56. package/skeleton/app/src/NewVersionNotice.tsx +63 -0
  57. package/skeleton/app/src/index.css +37 -0
  58. package/skeleton/app/src/main.tsx +18 -0
  59. package/skeleton/app/src/pwa.ts +18 -0
  60. package/skeleton/app/src/start.test.ts +15 -0
  61. package/skeleton/app/src/start.ts +9 -0
  62. package/skeleton/app/tsconfig.app.json +27 -0
  63. package/skeleton/app/tsconfig.json +8 -0
  64. package/skeleton/app/tsconfig.node.json +23 -0
  65. package/skeleton/app/tsconfig.test.json +10 -0
  66. package/skeleton/app/vite.config.ts +13 -0
  67. package/skills/app-workflow/SKILL.md +22 -0
  68. package/skills/update/SKILL.md +69 -0
  69. package/update-policy.json +35 -0
@@ -0,0 +1,41 @@
1
+ # Working in this apps repository
2
+
3
+ Scope: the whole repository. More specific app instructions can refine app work;
4
+ the owner's requests and recorded choices determine the task.
5
+
6
+ The owner describes what they need; you build small, usable steps for them to
7
+ try. Do not assume developer knowledge. Explain what to try and any choice in
8
+ everyday terms, in the owner's language. Make routine technical choices from
9
+ the workflow and installed SDK reference, and record them in app notes. Ask
10
+ only for the decisions that setup and the workflow leave to the owner, and use
11
+ answers already given.
12
+
13
+ The instructions you follow ship with the installed tooling package
14
+ `@sempods/apps` and the SDK. If `node_modules` is missing, follow the
15
+ bootstrap steps in [INIT.md](INIT.md) first.
16
+
17
+ 1. On first setup, read [INIT.md](INIT.md). On later sessions, read its setup
18
+ record and the target app's `apps/<id>/NOTES.md` first.
19
+ 2. Follow the app workflow,
20
+ `node_modules/@sempods/apps/instructions/app-workflow.md`, then the SDK's AI
21
+ entry, `node_modules/@sempods/app-sdk/docs/ai-app-builder.md`, and the
22
+ guides relevant to the change. They match the installed versions; use those
23
+ versions' API.
24
+ 3. Keep app-specific decisions in its notes and repository-wide choices in the
25
+ owner section below. Do not place credentials or private Pod data there.
26
+
27
+ The app-workflow skill in `.claude/skills/` and `.agents/skills/` routes to the
28
+ same workflow; skill discovery is optional. To update the tooling, follow the
29
+ update skill there, `node_modules/@sempods/apps/skills/update/SKILL.md`.
30
+ [User steps](docs/start.md) describe the same journey for the owner.
31
+
32
+ ## Owner instructions
33
+
34
+ Updates must preserve the delimited section below verbatim. Only change it to
35
+ record this owner's instructions or decisions.
36
+
37
+ <!-- BEGIN OWNER INSTRUCTIONS -->
38
+
39
+ No instance-specific instructions recorded yet.
40
+
41
+ <!-- END OWNER INSTRUCTIONS -->
@@ -0,0 +1,22 @@
1
+ packages:
2
+ - 'apps/*'
3
+
4
+ # Added dependencies get exact versions; installs require the Node version
5
+ # from engines.
6
+ saveExact: true
7
+ engineStrict: true
8
+
9
+ # Scripts fail on dependencies out of sync with the manifests instead of
10
+ # installing on their own, so check never rewrites pnpm-lock.yaml.
11
+ verifyDepsBeforeRun: error
12
+
13
+ # Dependencies run no install scripts: pnpm fails an install that would need
14
+ # one (strictDepBuilds). Approve a package deliberately under allowBuilds.
15
+
16
+ # Local installs have no minimum release age: pnpm would otherwise install a
17
+ # dependency younger than a day anyway when nothing older fits, add it to this
18
+ # file and stop the next `pnpm run` (verifyDepsBeforeRun). Dependabot applies
19
+ # a cooldown to regular version updates (.github/dependabot.yml). That is no
20
+ # universal age guarantee: local dependency changes, security updates and
21
+ # pnpm fallback paths can admit younger releases.
22
+ minimumReleaseAge: 0
@@ -0,0 +1,6 @@
1
+ name: sempods apps
2
+ # Explicit roots: the apps and the workflows. Keep them in the scan as the
3
+ # repository grows.
4
+ paths:
5
+ - apps
6
+ - .github
@@ -0,0 +1,38 @@
1
+ # My sempods apps
2
+
3
+ Describe a shopping list, a small tracker or another idea to your coding
4
+ assistant. Try the first version, then ask for the next change. Your
5
+ [sempods](https://www.sempods.org/) Pod keeps the data; the app gives it a
6
+ screen.
7
+
8
+ You need a coding assistant that can work with local files, Node.js in the
9
+ version `.node-version` names, pnpm, and a Pod account for trying real saves.
10
+ Your assistant helps with the local setup. A hosting account is needed only to
11
+ publish.
12
+
13
+ > **Preview.** This starter is new and still changing. Today you can build
14
+ > apps, try them locally against your Pod, update the SDK and the tooling, and
15
+ > publish all apps together on one site.
16
+
17
+ ## Start with your idea
18
+
19
+ Open this repository in your coding assistant and paste:
20
+
21
+ > Read AGENTS.md and INIT.md. Help me build a shopping list in German. Start
22
+ > with adding an item and marking it as bought. Show me how to try it on my Pod.
23
+
24
+ [Start, change, try, publish](docs/start.md) explains the next steps. Sign in
25
+ only in your browser; never paste passwords or tokens into the chat.
26
+
27
+ Apps include PWA configuration by default, for later installation from your
28
+ HTTPS site. That does not make data editing work offline.
29
+
30
+ For assistants: [AGENTS.md](AGENTS.md) and [INIT.md](INIT.md).
31
+
32
+ ## Licence
33
+
34
+ Your apps and this repository are yours; add the licence you choose. The
35
+ tooling package `@sempods/apps` is MIT-0. The SDK packages, including the
36
+ documentation and examples they ship, keep their own licences: Apache-2.0 for
37
+ code and CC BY 4.0 for documentation. Code adapted from SDK examples keeps its
38
+ Apache-2.0 notice.
@@ -0,0 +1,28 @@
1
+ name: Check
2
+ # Checks the apps as they are: manifest, generated configuration, the shared
3
+ # SDK version, and lint, build and tests of each app, also on its own.
4
+ on:
5
+ pull_request:
6
+ push:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+ permissions:
10
+ contents: read
11
+ jobs:
12
+ check:
13
+ # A ruleset can require this check by name; keep it stable.
14
+ name: Apps
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 20
17
+ steps:
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ with:
20
+ persist-credentials: false
21
+ # Installs the pnpm version that packageManager in package.json pins.
22
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
23
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
24
+ with:
25
+ node-version-file: .node-version
26
+ cache: pnpm
27
+ - run: pnpm install --frozen-lockfile
28
+ - run: pnpm run check --standalone all
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "apps": []
4
+ }
@@ -0,0 +1,23 @@
1
+ node_modules/
2
+ dist/
3
+ site-dist/
4
+ .netlify/
5
+ *.tsbuildinfo
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ .DS_Store
10
+ dev-dist/
11
+ # Personal assistant instructions stay local.
12
+ CLAUDE.local.md
13
+ AGENTS.override.md
14
+
15
+ # Editor settings stay local.
16
+ .idea/
17
+ .vscode/
18
+
19
+ # Local SDK update checkpoint; removed only after install and checks succeed.
20
+ /.sdk-update-pending
21
+ # Open starter migration: manual cases, their sources and suggestions;
22
+ # removed once the migration is complete.
23
+ /.sempods-migration/
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: update
3
+ description:
4
+ Update the tooling of this sempods apps repository: install a newer
5
+ @sempods/apps, migrate the shared files, review and open a pull request.
6
+ Not for SDK-only updates.
7
+ ---
8
+
9
+ Read the canonical update skill that the installed tooling package ships,
10
+ `node_modules/@sempods/apps/skills/update/SKILL.md`. If `node_modules` is
11
+ missing, follow the bootstrap steps in `INIT.md` first.
@@ -0,0 +1,78 @@
1
+ # Version updates. Security updates are enabled in the repository settings.
2
+ version: 2
3
+
4
+ updates:
5
+ # The pinned `uses:` SHAs in the workflows are supply-chain dependencies too.
6
+ - package-ecosystem: 'github-actions'
7
+ directory: '/'
8
+ schedule:
9
+ interval: 'weekly'
10
+ # Reviewed releases of shared sempods actions need no waiting period;
11
+ # third-party actions keep Dependabot's default three-day cooldown.
12
+ cooldown:
13
+ exclude:
14
+ - 'sempods/.github/*'
15
+ groups:
16
+ github-actions:
17
+ patterns:
18
+ - '*'
19
+ update-types:
20
+ - 'minor'
21
+ - 'patch'
22
+
23
+ # Dependabot's npm ecosystem updates pnpm-lock.yaml too, and every
24
+ # package.json of the pnpm workspace: the root and each app.
25
+ - package-ecosystem: 'npm'
26
+ directory: '/'
27
+ schedule:
28
+ interval: 'weekly'
29
+ versioning-strategy: 'increase'
30
+ # Regular version updates wait three days; Dependabot also attempts an age
31
+ # gate for newly resolved transitive dependencies. Security updates skip
32
+ # it. sempods releases are reviewed upstream and need no waiting period.
33
+ cooldown:
34
+ default-days: 3
35
+ exclude:
36
+ - '@sempods/*'
37
+ # Ignore rules with update-types stop only version updates; security
38
+ # updates for these packages still arrive.
39
+ ignore:
40
+ # BEGIN OWNER SEMPODS UPDATES
41
+ # No version PRs for the SDK and the tooling. Remove the entry between
42
+ # these markers to receive them; updates keep this section as it is.
43
+ - dependency-name: '@sempods/*'
44
+ update-types:
45
+ - 'version-update:semver-major'
46
+ - 'version-update:semver-minor'
47
+ - 'version-update:semver-patch'
48
+ # END OWNER SEMPODS UPDATES
49
+ # Match the Node 24 runtime baseline; review runtime and typings together.
50
+ - dependency-name: '@types/node'
51
+ update-types:
52
+ - 'version-update:semver-major'
53
+ # A dependency joins the first group it matches. Both SDK packages move
54
+ # together in one PR; the tooling has its own, since it brings a starter
55
+ # migration (update skill). Security updates move only affected packages:
56
+ # their SDK group joins advisories for both, and a PR that moves one is
57
+ # completed with sdk-update (Update the SDK in the app workflow).
58
+ groups:
59
+ sempods-sdk:
60
+ patterns:
61
+ - '@sempods/app-sdk'
62
+ - '@sempods/client-sdk'
63
+ sempods-sdk-security:
64
+ applies-to: 'security-updates'
65
+ patterns:
66
+ - '@sempods/app-sdk'
67
+ - '@sempods/client-sdk'
68
+ sempods-apps:
69
+ patterns:
70
+ - '@sempods/apps'
71
+ npm:
72
+ patterns:
73
+ - '*'
74
+ exclude-patterns:
75
+ - '@sempods/*'
76
+ update-types:
77
+ - 'minor'
78
+ - 'patch'
@@ -0,0 +1,57 @@
1
+ # Set up this repository
2
+
3
+ This is the assistant's starting point for setup. Users can start with
4
+ [the short guide](docs/start.md). Follow [AGENTS.md](AGENTS.md) and preserve the
5
+ setup record below on later runs and updates.
6
+
7
+ ## Bootstrap
8
+
9
+ The setup procedure ships with the tooling package, so install the
10
+ dependencies first. Check `node --version` against `.node-version`. If it
11
+ differs, switch with the owner's version manager and name that version
12
+ explicitly; not every manager reads `.node-version` by default, and with mise
13
+ prefix each command with `mise exec node@<version> --`. Do not write a
14
+ version-manager configuration into the repository: `.node-version` stays the
15
+ only source. Otherwise ask the owner to install that version; do not work
16
+ around it. `pnpm-workspace.yaml` sets `engineStrict`, so `pnpm install` stops
17
+ on an older Node.
18
+
19
+ Then check `pnpm --version`. If pnpm is missing, Node 24 brings Corepack:
20
+ `corepack enable pnpm`. Without Corepack (Node 25 and later), or if it cannot
21
+ write to Node's directory, install pnpm globally with npm after asking the
22
+ owner. pnpm switches to the version that `packageManager` in `package.json`
23
+ pins. From the repository root:
24
+
25
+ ```sh
26
+ pnpm install
27
+ ```
28
+
29
+ A repository that has committed `pnpm-lock.yaml` installs with
30
+ `pnpm install --frozen-lockfile` instead. Commit the lockfile the first install
31
+ writes.
32
+
33
+ ## Setup procedure
34
+
35
+ Follow `node_modules/@sempods/apps/instructions/setup.md`. It covers the first
36
+ app, optional repository preferences and when setup is done. Record progress
37
+ only in the setup record below.
38
+
39
+ ## Setup record
40
+
41
+ The assistant updates only this record as setup progresses; updates preserve
42
+ it. Keep it free of credentials and private data.
43
+
44
+ <!-- BEGIN INSTANCE SETUP RECORD -->
45
+
46
+ - Status: not started
47
+ - Created from (`.sempods-baseline.json`): not recorded
48
+ - SDK version at setup (`@sempods/app-sdk` in the root `package.json`): not recorded
49
+ - Owner and review identity: not recorded
50
+ - UI language: not selected
51
+ - First app ID and idea: not selected
52
+ - Test Pod/context: not selected (optional; synthetic data only)
53
+ - sempods version PRs (Dependabot): not chosen
54
+ - Local URL and check result: not checked
55
+ - Pod exercise and remaining gaps: not tested
56
+
57
+ <!-- END INSTANCE SETUP RECORD -->
@@ -0,0 +1,5 @@
1
+ # Repository instructions
2
+
3
+ Read [AGENTS.md](AGENTS.md). It routes to setup, the app workflow and tooling
4
+ updates. The [app-workflow skill](.claude/skills/app-workflow/SKILL.md) uses
5
+ the same instructions.
@@ -0,0 +1,35 @@
1
+ name: CodeQL
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches: [main]
6
+ schedule:
7
+ - cron: '41 6 * * 2'
8
+ workflow_dispatch:
9
+ permissions:
10
+ contents: read
11
+ jobs:
12
+ analyze:
13
+ name: Analyze (${{ matrix.language }})
14
+ runs-on: ubuntu-latest
15
+ timeout-minutes: 20
16
+ permissions:
17
+ contents: read
18
+ security-events: write
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ language: [actions, javascript-typescript]
23
+ steps:
24
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
25
+ with:
26
+ persist-credentials: false
27
+ - uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
28
+ with:
29
+ languages: ${{ matrix.language }}
30
+ build-mode: none
31
+ config-file: ./.github/codeql/codeql-config.yml
32
+ queries: security-extended
33
+ - uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
34
+ with:
35
+ category: /language:${{ matrix.language }}
@@ -0,0 +1,25 @@
1
+ {
2
+ "format": 1,
3
+ "revision": "71735f2f98610875bee974bb9b42651505d4b395dcdb02eb8ac8ff9ed48c6e2c",
4
+ "files": {
5
+ ".agents/skills/app-workflow/SKILL.md": "0221b70340152ba3cc615342254603b29b89c4d7d944d7b3b3e647873bdcefa0",
6
+ ".agents/skills/update/SKILL.md": "d3ca3a13c6524a2ee9382977ddc0539790813225f366306024a58c957a4716ee",
7
+ ".claude/skills/app-workflow/SKILL.md": "0221b70340152ba3cc615342254603b29b89c4d7d944d7b3b3e647873bdcefa0",
8
+ ".claude/skills/update/SKILL.md": "d3ca3a13c6524a2ee9382977ddc0539790813225f366306024a58c957a4716ee",
9
+ ".github/codeql/codeql-config.yml": "528d7544990228d6d66fa62423775338fe1716326fc68371fb2665aee86e39b0",
10
+ ".github/dependabot.yml": "e13474647ebaf26d042d76f4917203d209a2b14c0d2af8d12139ea328530823e",
11
+ ".github/workflows/check.yml": "8b7567319e18694aeca8722107281a760e3100239c6d0f548acb71e1806fa172",
12
+ ".github/workflows/codeql.yml": "ff9f774d9732fdc7d704ef37ddc40ef507a1bab7ee3c1db5b1a6a8f26e6e4b37",
13
+ ".gitignore": "c2a4b026a87f6c8e7473e7fc456c6020b63c7e675087f6b58013ed5163c2f6ee",
14
+ ".node-version": "b911698dc3cf3227d34d0ad6727f1c4408791203c9665de99ab959331a1fb07d",
15
+ "AGENTS.md": "393b09ecd18889d9e1fdf2a6df12d1f6c193fc7fcd89ba808ad94df521bad8db",
16
+ "CLAUDE.md": "f9e2544290ee8418484e47194444ee58c2ea675682871ae8a8727d1edb17978e",
17
+ "INIT.md": "f2fb601d5ebc4ca72274ac9d3fcf7b7a96e27958bb92cd065a9165b501ee94d7",
18
+ "README.md": "676e6bf33b44044fb4c62c9fe5441729bd47f2d5077ff904e57a4a1b658859ac",
19
+ "apps.json": "9945f9bd573e5af8aad2e65cc8b4284fbb1d7e8b96c4efdd42eeb49132fb4327",
20
+ "apps/.gitkeep": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
21
+ "docs/start.md": "1c29eed0a0297c742ca5814de9081ff8efce3b2da5d9e1bdf90a403873da4aa9",
22
+ "package.json": "31ede0d1a035d8531100ebfd751510c772ab0871309a0b9752a77fb96187eb53",
23
+ "pnpm-workspace.yaml": "4660ca1887261ae658c7f587190e2b71ac91d15b1b6c4cf04dec823fdd0da654"
24
+ }
25
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["react", "typescript", "oxc"],
4
+ "rules": {
5
+ "react/rules-of-hooks": "error",
6
+ "react/only-export-components": [
7
+ "warn",
8
+ {
9
+ "allowConstantExport": true
10
+ }
11
+ ]
12
+ },
13
+ "ignorePatterns": ["node_modules", "dist", "dev-dist"]
14
+ }
@@ -0,0 +1,19 @@
1
+ # __APP_TITLE__: development notes
2
+
3
+ Your assistant keeps these notes up to date so the next session can continue.
4
+
5
+ ## Idea
6
+
7
+ What the app is for and its first useful interaction.
8
+
9
+ ## Vocabulary
10
+
11
+ Types and properties (full IRIs) the app reads and writes.
12
+
13
+ ## Decisions
14
+
15
+ Choices the owner made, with the date.
16
+
17
+ ## Tested
18
+
19
+ What was tried, against which Pod and context, and what is still untested.
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="__APP_LANGUAGE__">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta name="theme-color" content="#176354" />
8
+ <!-- The callback page must not send its code and state in Referer. -->
9
+ <meta name="referrer" content="strict-origin" />
10
+ <title>__APP_TITLE__</title>
11
+ </head>
12
+ <body>
13
+ <div id="root"></div>
14
+ <script type="module" src="/src/main.tsx"></script>
15
+ </body>
16
+ </html>
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "__APP_ID__",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "oxlint",
10
+ "test": "vitest run --passWithNoTests",
11
+ "preview": "vite preview"
12
+ },
13
+ "dependencies": {
14
+ "react": "19.3.0",
15
+ "react-dom": "19.3.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "24.19.1",
19
+ "@types/react": "19.3.0",
20
+ "@types/react-dom": "19.3.0",
21
+ "@vitejs/plugin-react": "6.1.2",
22
+ "oxlint": "1.87.0",
23
+ "typescript": "7.0.2",
24
+ "vite": "8.3.3",
25
+ "vite-plugin-pwa": "2.0.0",
26
+ "vitest": "5.0.3"
27
+ }
28
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#176354"/></svg>
Binary file
Binary file
@@ -0,0 +1,73 @@
1
+ import { useRef, useState } from 'react';
2
+ import { createBrowserRuntime } from '@sempods/app-sdk';
3
+ import {
4
+ AppAccess,
5
+ SempodsProvider,
6
+ TargetScreen,
7
+ useAppState,
8
+ useSdkLocale,
9
+ } from '@sempods/app-sdk/react';
10
+ import { NewVersionNotice } from './NewVersionNotice.tsx';
11
+ import { app, runtimeOptions } from './sempods.generated.ts';
12
+ import { startMessage } from './start.ts';
13
+
14
+ // One runtime for the app's lifetime, created outside rendering.
15
+ const runtime = createBrowserRuntime(runtimeOptions);
16
+
17
+ // Vite hot replacement ends this module's runtime lifetime.
18
+ if (import.meta.hot) import.meta.hot.dispose(() => runtime.dispose());
19
+
20
+ export default function App({
21
+ registration,
22
+ }: {
23
+ readonly registration: Promise<ServiceWorkerRegistration | undefined>;
24
+ }) {
25
+ return (
26
+ <SempodsProvider runtime={runtime} language={app.language}>
27
+ <NewVersionNotice registration={registration} />
28
+ <Frame />
29
+ </SempodsProvider>
30
+ );
31
+ }
32
+
33
+ // The app owns its layout. AppAccess shows sign-in and recovery beside the
34
+ // content and hides once a context is readable; the button reopens it.
35
+ function Frame() {
36
+ const [open, setOpen] = useState(false);
37
+ const focusTarget = useRef<HTMLButtonElement>(null);
38
+ const { connections } = useAppState();
39
+ const { messages } = useSdkLocale();
40
+ const header = connections.length > 0;
41
+ return (
42
+ <main>
43
+ {header && (
44
+ <header>
45
+ <h1>{app.title}</h1>
46
+ <button
47
+ ref={focusTarget}
48
+ aria-expanded={open}
49
+ onClick={() => setOpen(!open)}
50
+ >
51
+ {messages.controls.dataAccess}
52
+ </button>
53
+ </header>
54
+ )}
55
+ {/* Below the header's h1, AppAccess uses h2. */}
56
+ <AppAccess
57
+ appName={app.title}
58
+ headingLevel={header ? 2 : 1}
59
+ open={open}
60
+ focusTarget={focusTarget}
61
+ />
62
+ <TargetScreen>
63
+ <Start />
64
+ </TargetScreen>
65
+ </main>
66
+ );
67
+ }
68
+
69
+ // Replace this screen with your app. It renders once a Pod context is chosen.
70
+ function Start() {
71
+ const { language } = useSdkLocale();
72
+ return <p>{startMessage(language)}</p>;
73
+ }
@@ -0,0 +1,63 @@
1
+ // Adapted from the sempods TypeScript SDK v0.2.0 recipe
2
+ // examples/todo/pwa/register.tsx, licensed under Apache-2.0; see
3
+ // the LICENSE and NOTICE of the @sempods/app-sdk package. Changes: this header; the
4
+ // registration function moved to pwa.ts.
5
+ import { useEffect, useState } from 'react';
6
+ import { useSdkLocale } from '@sempods/app-sdk/react';
7
+
8
+ /**
9
+ * Render beneath the app's actual locale provider so notices follow its language.
10
+ * Keep it outside target/access gates: updates also matter while signed out.
11
+ * Announces a downloaded version. It applies once every window of the app is
12
+ * closed, so an update never reloads a page over unsaved drafts or unconfirmed
13
+ * writes. App-owned recipe, not an SDK export.
14
+ */
15
+ export function NewVersionNotice({
16
+ registration,
17
+ }: {
18
+ readonly registration: Promise<ServiceWorkerRegistration | undefined>;
19
+ }) {
20
+ const [ready, setReady] = useState(false);
21
+ const { language } = useSdkLocale();
22
+ useEffect(() => {
23
+ let active = true;
24
+ let stop = () => {};
25
+ void registration.then((r) => {
26
+ if (!r || !active) return;
27
+ // Only an update of a controlled page is news; a first install is not.
28
+ const check = () => {
29
+ if (active && r.waiting && navigator.serviceWorker.controller)
30
+ setReady(true);
31
+ };
32
+ // Follow a worker that was already installing before this subscription
33
+ // (updatefound fired earlier) as well as every later one.
34
+ const watched = new Set<ServiceWorker>();
35
+ const watch = (worker: ServiceWorker | null) => {
36
+ if (!worker || watched.has(worker)) return;
37
+ watched.add(worker);
38
+ worker.addEventListener('statechange', check);
39
+ };
40
+ const found = () => watch(r.installing);
41
+ r.addEventListener('updatefound', found);
42
+ watch(r.installing);
43
+ stop = () => {
44
+ r.removeEventListener('updatefound', found);
45
+ for (const worker of watched)
46
+ worker.removeEventListener('statechange', check);
47
+ };
48
+ check();
49
+ });
50
+ return () => {
51
+ active = false;
52
+ stop();
53
+ };
54
+ }, [registration]);
55
+ if (!ready) return null;
56
+ return (
57
+ <p role="status">
58
+ {language === 'de'
59
+ ? 'Eine neue Version ist bereit. Sie wird verwendet, sobald alle Fenster dieser App geschlossen sind.'
60
+ : 'A new version is ready. It is used once every window of this app has been closed.'}
61
+ </p>
62
+ );
63
+ }
@@ -0,0 +1,37 @@
1
+ :root {
2
+ /* Follow the system's light or dark mode; the SDK components follow the page.
3
+ The colours match the SDK defaults, so AppAccess sits seamlessly on the page. */
4
+ color-scheme: light dark;
5
+ font-family: system-ui, sans-serif;
6
+ color: light-dark(#202923, #e8eee9);
7
+ background: light-dark(#fff, #191b1a);
8
+ }
9
+ body {
10
+ margin: 0;
11
+ }
12
+ main {
13
+ max-width: 760px;
14
+ margin: auto;
15
+ padding: 16px;
16
+ }
17
+ header {
18
+ display: flex;
19
+ flex-wrap: wrap;
20
+ align-items: center;
21
+ justify-content: space-between;
22
+ gap: 0.5rem;
23
+ }
24
+ header h1 {
25
+ margin: 0;
26
+ font-size: 1.4rem;
27
+ }
28
+ button,
29
+ input,
30
+ select {
31
+ font: inherit;
32
+ margin: 0.25rem;
33
+ padding: 0.4rem;
34
+ }
35
+ input[type='checkbox'] {
36
+ accent-color: light-dark(#226044, #8acda6);
37
+ }