@signalk/app-dock 1.2.0 → 1.2.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.
@@ -0,0 +1,37 @@
1
+ # Categories for the release notes GitHub generates for each tag, built from
2
+ # the titles of the PRs merged since the previous one. release-please asks
3
+ # GitHub for these notes (changelog-type: github in
4
+ # release-please-config.json), and so does release_on_tag.yml for a
5
+ # pre-release tag pushed by hand.
6
+ #
7
+ # A PR lands in the first category it matches, so the catch-all is last.
8
+ # workflows/label-by-title.yml sets the label from the PR title's type, and
9
+ # this file and that workflow name the same labels. A label set by hand
10
+ # places a PR the same way until its title is edited; an unlabelled one
11
+ # still appears, under Other.
12
+ changelog:
13
+ exclude:
14
+ labels:
15
+ - skip-changelog
16
+ # release-please's own release PR, merged right before each tag, which
17
+ # would otherwise list itself.
18
+ - 'autorelease: pending'
19
+ - 'autorelease: tagged'
20
+ categories:
21
+ - title: 🚀 Features
22
+ labels:
23
+ - feature
24
+ - enhancement
25
+ - title: 🐛 Fixes
26
+ labels:
27
+ - bug
28
+ - fix
29
+ - title: 📖 Documentation
30
+ labels:
31
+ - documentation
32
+ - title: 📦 Dependencies
33
+ labels:
34
+ - dependencies
35
+ - title: Other
36
+ labels:
37
+ - '*'
@@ -0,0 +1,67 @@
1
+ name: label-by-title
2
+
3
+ on:
4
+ pull_request_target:
5
+ types: [opened, edited, reopened]
6
+
7
+ permissions: {}
8
+
9
+ jobs:
10
+ # .github/release.yml sorts the release notes by label, and a pull request
11
+ # opened from the command line carries none. The label follows the title's
12
+ # conventional-commit type. Only the title is read, with no checkout and
13
+ # nothing from the pull request run here, which is what makes
14
+ # pull_request_target safe to use and what lets a pull request from a fork
15
+ # be labelled.
16
+ #
17
+ # `edited` also fires for a body or base-branch edit; those leave the labels
18
+ # alone, so a label set by hand stays until the title changes.
19
+ label:
20
+ if: ${{ github.event.action != 'edited' || github.event.changes.title }}
21
+ # One run per pull request at a time: two overlapping runs would each read
22
+ # the labels, then each edit, and could leave two managed labels behind.
23
+ # Set on the job, after the condition above, so that a body edit, which
24
+ # skips the job, cannot cancel a title edit's run.
25
+ concurrency:
26
+ group: label-by-title-${{ github.event.pull_request.number }}
27
+ cancel-in-progress: true
28
+ runs-on: ubuntu-latest
29
+ timeout-minutes: 5
30
+ permissions:
31
+ pull-requests: write
32
+ steps:
33
+ - env:
34
+ GH_TOKEN: ${{ github.token }}
35
+ REPO: ${{ github.repository }}
36
+ NUMBER: ${{ github.event.pull_request.number }}
37
+ TITLE: ${{ github.event.pull_request.title }}
38
+ run: |
39
+ # The labels this workflow owns: one of them at a time, so a title
40
+ # edited from feat to fix swaps the label rather than adding one.
41
+ # A type not listed gets none of them and lands under Other.
42
+ managed="enhancement bug documentation skip-changelog"
43
+ conventional='^([a-z]+)(\([^)]*\))?!?: '
44
+ type=""
45
+ if [[ "$TITLE" =~ $conventional ]]; then
46
+ type="${BASH_REMATCH[1]}"
47
+ fi
48
+ case "$type" in
49
+ feat|perf) wanted=enhancement ;;
50
+ fix) wanted=bug ;;
51
+ docs) wanted=documentation ;;
52
+ build|ci|test|chore|refactor|style) wanted=skip-changelog ;;
53
+ *) wanted="" ;;
54
+ esac
55
+ current=$(gh pr view "$NUMBER" --repo "$REPO" --json labels --jq '[.labels[].name] | join(" ")')
56
+ args=()
57
+ for label in $managed; do
58
+ case " $current " in *" $label "*) has=1 ;; *) has=0 ;; esac
59
+ if [ "$label" = "$wanted" ] && [ "$has" = 0 ]; then args+=(--add-label "$label"); fi
60
+ if [ "$label" != "$wanted" ] && [ "$has" = 1 ]; then args+=(--remove-label "$label"); fi
61
+ done
62
+ if [ "${#args[@]}" = 0 ]; then
63
+ echo "#$NUMBER '$TITLE': nothing to change (${wanted:-no managed label})"
64
+ else
65
+ gh pr edit "$NUMBER" --repo "$REPO" "${args[@]}"
66
+ echo "#$NUMBER '$TITLE': ${args[*]}"
67
+ fi
@@ -0,0 +1,109 @@
1
+ name: release-please
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ # The gate below runs release-please only for a push that carries a commit
7
+ # users get, so a release pull request it has already opened is not
8
+ # refreshed by a docs or ci merge, and a config change is exactly such a
9
+ # merge. This trigger re-runs it on demand, which is what rewrites an
10
+ # outstanding release pull request against the current configuration.
11
+ workflow_dispatch:
12
+
13
+ permissions: {}
14
+
15
+ # One release-please run at a time: a push and a manual run, or two pushes
16
+ # landing close together, would each read the release pull request's state
17
+ # and each write it, and the later write could undo the earlier one.
18
+ # cancel-in-progress is false so a run already creating a tag or dispatching
19
+ # the publish is never killed part-way. A run waiting behind it can still be
20
+ # replaced by a newer one, which costs nothing here: release-please derives
21
+ # the release pull request from the commit range, so the newest run produces
22
+ # the same result the superseded one would have.
23
+ #
24
+ # The group is a constant rather than keyed on github.ref: the action is
25
+ # given no target-branch, so every run, including a dispatch launched from
26
+ # some other branch, acts on the default branch's release pull request.
27
+ concurrency:
28
+ group: release-please
29
+ cancel-in-progress: false
30
+
31
+ jobs:
32
+ # GitHub's release notes are never empty, so release-please would propose
33
+ # a release after every merge, a docs or ci change included. Only a push
34
+ # carrying a change users get, or the merge of the release PR itself,
35
+ # which is what creates the tag, goes on to it.
36
+ #
37
+ # Every commit in the push is checked, so a merge commit's PR commits count
38
+ # through their own subjects. A breaking change counts whatever its type, a
39
+ # `revert:` because what it undoes has shipped, and a `Release-As:` footer,
40
+ # matched in any case as release-please does, because it asks for a release
41
+ # on any subject. GitHub's own `Revert "…"` subject is not conventional, so
42
+ # release-please ignores it and it is not let through here either. The last
43
+ # alternative is the release PR's own title, pull-request-title-pattern in
44
+ # release-please-config.json, and must change with it, or the release PR's
45
+ # merge is judged non-releasable and no tag is ever created. The payload
46
+ # holds at most 2048 commits, so a push that fills it may hide more and is
47
+ # let through.
48
+ gate:
49
+ runs-on: ubuntu-latest
50
+ timeout-minutes: 5
51
+ outputs:
52
+ releasable: ${{ steps.check.outputs.releasable }}
53
+ steps:
54
+ # A manual run carries no commits to judge, and asking for one is the
55
+ # statement that it should happen, so it bypasses the check below
56
+ # rather than being skipped by it for having an empty payload.
57
+ - id: check
58
+ env:
59
+ DISPATCHED: ${{ github.event_name == 'workflow_dispatch' }}
60
+ MESSAGES: ${{ toJSON(github.event.commits.*.message) }}
61
+ RELEASABLE: '^((feat|fix|perf|revert)(\([^)]*\))?!?: |[a-z]+(\([^)]*\))?!: |chore\(release\): v?[0-9])'
62
+ run: |
63
+ if [ "$DISPATCHED" = true ]; then
64
+ echo "releasable=true" >> "$GITHUB_OUTPUT"
65
+ echo "Run requested manually; release-please not gated."
66
+ exit 0
67
+ fi
68
+ if jq -e --arg re "$RELEASABLE" '(length >= 2048) or ([.[] | (split("\n")[0] | test($re)) or test("\nBREAKING[- ]CHANGE: ") or test("\nrelease-as: "; "i")] | any)' <<< "$MESSAGES" > /dev/null; then
69
+ echo "releasable=true" >> "$GITHUB_OUTPUT"
70
+ else
71
+ echo "releasable=false" >> "$GITHUB_OUTPUT"
72
+ echo "No releasable commit in this push; release-please not run."
73
+ fi
74
+
75
+ release-please:
76
+ needs: gate
77
+ if: ${{ needs.gate.outputs.releasable == 'true' }}
78
+ runs-on: ubuntu-latest
79
+ timeout-minutes: 10
80
+ permissions:
81
+ contents: write
82
+ pull-requests: write
83
+ # release-please creates its `autorelease: pending` / `autorelease:
84
+ # tagged` labels through the issues API the first time it needs them.
85
+ issues: write
86
+ outputs:
87
+ releases_created: ${{ steps.rp.outputs.releases_created }}
88
+ tag_name: ${{ steps.rp.outputs.tag_name }}
89
+ steps:
90
+ - uses: googleapis/release-please-action@v5
91
+ id: rp
92
+
93
+ # The tag release-please just pushed does not start release_on_tag.yml by
94
+ # itself: an event raised with GITHUB_TOKEN starts no workflow, with
95
+ # workflow_dispatch as the exception. So release_on_tag.yml is dispatched on
96
+ # the tag. It has to stay the file that publishes, since npm's trusted
97
+ # publishing is bound to that workflow file. This is its own job, so that
98
+ # the right to start workflows is held by nothing but this one command.
99
+ publish:
100
+ needs: release-please
101
+ if: ${{ needs.release-please.outputs.releases_created == 'true' }}
102
+ runs-on: ubuntu-latest
103
+ timeout-minutes: 5
104
+ permissions:
105
+ actions: write
106
+ steps:
107
+ - env:
108
+ GH_TOKEN: ${{ github.token }}
109
+ run: gh workflow run release_on_tag.yml --repo "$GITHUB_REPOSITORY" --ref "${{ needs.release-please.outputs.tag_name }}"
@@ -1,16 +1,27 @@
1
+ # Publishes the tag it runs on to npm. Release tags come from release-please:
2
+ # merging its release PR creates the tag and the GitHub Release, then
3
+ # release-please.yml dispatches this workflow on the tag. Pre-release tags
4
+ # (-beta.N, -rc.N, -alpha.N) are pushed by hand, and only a pushed tag gets
5
+ # its GitHub Release created here.
1
6
  name: 'Release on tag'
2
7
 
3
8
  on:
4
9
  push:
5
10
  tags:
6
11
  - 'v*'
12
+ # How release-please.yml starts this on a tag it created: that tag, pushed
13
+ # with GITHUB_TOKEN, raises no push event.
14
+ workflow_dispatch:
7
15
 
8
16
  concurrency:
9
17
  group: release-${{ github.ref }}
10
18
  cancel-in-progress: false
11
19
 
12
20
  jobs:
21
+ # A dispatched run is on a tag release-please made, and release-please made
22
+ # its Release too; creating one here would be a second for the same tag.
13
23
  release:
24
+ if: ${{ github.event_name == 'push' }}
14
25
  permissions:
15
26
  contents: write
16
27
  runs-on: ubuntu-latest
@@ -28,6 +39,10 @@ jobs:
28
39
 
29
40
  publish:
30
41
  needs: release
42
+ # Runs after the Release on a pushed tag, and without it on a dispatched
43
+ # one, where that job is skipped. A dispatch can name any ref; only a
44
+ # release tag is published.
45
+ if: ${{ !cancelled() && needs.release.result != 'failure' && startsWith(github.ref, 'refs/tags/v') }}
31
46
  permissions:
32
47
  id-token: write
33
48
  contents: read
@@ -43,6 +58,16 @@ jobs:
43
58
 
44
59
  - run: npm ci
45
60
 
61
+ - name: Verify the tag matches package.json
62
+ run: |
63
+ TAG="${GITHUB_REF_NAME#v}"
64
+ PKG="$(node -p "require('./package.json').version")"
65
+ if [ "$TAG" != "$PKG" ]; then
66
+ echo "::error::tag $GITHUB_REF_NAME does not match package.json version $PKG"
67
+ exit 1
68
+ fi
69
+ echo "tag and package.json agree on $PKG"
70
+
46
71
  - name: Publish to npm
47
72
  run: |
48
73
  tag="${GITHUB_REF#refs/tags/}"
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.2.1"
3
+ }
package/AGENTS.md ADDED
@@ -0,0 +1,134 @@
1
+ # @signalk/app-dock
2
+
3
+ A Signal K server plugin plus webapp: a macOS-style dock for switching between Signal K webapps on touch screens. The
4
+ dock page holds each app in an iframe, and a double-tap anywhere brings up the dock. User-facing behaviour is documented
5
+ in `README.md`; this file covers what a contributor needs to keep intact.
6
+
7
+ ## Architecture
8
+
9
+ Three surfaces ship together from one package:
10
+
11
+ - **`plugin/index.js`**: the server plugin (CommonJS). It holds the schema, seeds the default apps on first start, serves
12
+ the dock's HTTP routes, and registers the `environment.mode` PUT handler for the night-mode button.
13
+ - **`public/dock.js`, `dock.css`, `index.html`**: the dock itself, served as-is at `/@signalk/app-dock/`. Plain browser
14
+ JavaScript with **no build step**: edit and reload.
15
+ - **`src/configpanel/`**: the React 19 config panel that the admin UI loads through Webpack Module Federation (keyword
16
+ `signalk-plugin-configurator`). `npm run build:config` writes `public/remoteEntry.js` and its chunks.
17
+
18
+ `public/config.html` is an older standalone configurator that the schema description links to; the admin UI shows the
19
+ React panel.
20
+
21
+ ## Rules to keep in mind
22
+
23
+ - **Seed once, never rewrite.** `start()` seeds `DEFAULT_APPS` only when the saved `apps` is not an array. An app list
24
+ that already exists, even an empty one, is the user's and is never changed.
25
+ - **The default app list lives in three places.** `DEFAULT_APPS` in `plugin/index.js`, `NO_ADMIN_DEFAULTS` in
26
+ `public/dock.js` (served to visitors the plugin's `/settings` refuses), and the welcome-tour text in
27
+ `public/index.html`. Change them together.
28
+ - **`savePluginOptions` replaces the whole saved configuration.** Build every save from a complete configuration
29
+ (`running ? pluginSettings : savedSettings()`), never from a partial object.
30
+ - **Routes must work while the plugin is stopped.** signalk-server registers the router of every loaded plugin, enabled
31
+ or not. A handler that needs the configuration reads `app.readPluginOptions()` when `running` is false.
32
+ - **Access is opt-in per route.** Routes on `router` are admin-only. `router.access('readonly' | 'readwrite')`
33
+ (signalk-server 2.31 and later) opens a route to more users; feature-detect it (`typeof router.access === 'function'`)
34
+ so older servers keep working admin-only. `/config` stays admin-only on every server. When access changes, update the
35
+ access detection at the top of `dock.js` and the **Access** table in `README.md` with it.
36
+ - **`/settings` is readable by every visitor with read access.** Never put credentials or anything secret in it; that
37
+ includes app URLs.
38
+ - **Taps pass through.** The double-tap listener is passive, never calls `preventDefault()`, and there is no overlay
39
+ element. It attaches to the dock document and, recursively, to every same-origin iframe document, so apps that embed
40
+ other apps still open the dock.
41
+ - **One iframe sandbox string.** `switchToApp()` and the tour's Plugin Config link in `dock.js` create iframes with the
42
+ same `sandbox` value; keep them identical. `allow-top-navigation-by-user-activation` is what lets a dock opened inside
43
+ a dock break out to the top window (the guard at the top of `dock.js`).
44
+ - **Live config reload.** The dock polls the plugin every 5 s. A setting that changes how the dock is built belongs in
45
+ `STRUCTURAL_KEYS` in `dock.js` (the page reloads); app-list changes rebuild the dock in place.
46
+ - **New settings touch four places:** the schema in `plugin/index.js`, `DEFAULTS` in `dock.js`, the config panel in
47
+ `src/configpanel/`, and the **Settings** table in `README.md`.
48
+ - **Browser globals are declared.** ESLint lints `dock.js` as a classic script with an explicit globals list in
49
+ `eslint.config.js`; add a browser API there when `dock.js` starts using it.
50
+
51
+ ## Build output is committed
52
+
53
+ `public/remoteEntry.js`, `public/main.js`, `public/540.js`, `public/805.js` and `public/540.js.LICENSE.txt` are Webpack
54
+ output and are tracked in git. After changing `src/configpanel/`, run `npm run build:config` and commit the regenerated
55
+ files with the source change. `prepublishOnly` rebuilds them at publish time. Prettier and ESLint skip them, and ESLint
56
+ also skips `src/`.
57
+
58
+ ## Icons
59
+
60
+ `public/app-icon.svg` is the source icon: the `signalk.appIcon` shown by the admin UI and the App Store, the favicon,
61
+ and the dock's own logo. `public/app-icon-180.png` (apple-touch-icon) is generated from it and
62
+ `public/app-icon-maskable.svg` is maintained by hand; see **Icon assets** in `README.md`.
63
+
64
+ ## Tests
65
+
66
+ `npm test` runs `test/plugin.test.js` with `node:test` against a mock `app` (`createMockApp()`). Cover new routes,
67
+ settings and seeding behaviour there. `dock.js` has no automated tests; check dock changes in a browser against a
68
+ running server (see **Development** in `README.md`).
69
+
70
+ ## Workflow conventions
71
+
72
+ This repo is maintained by Dirk Wahrheit.
73
+
74
+ - Branch names use **hyphens**, never slashes.
75
+ - Angular conventional commits: `<type>(<scope>): <subject>`. PR titles use the same format; the generated release
76
+ notes are built from them.
77
+ - The PR title's type also picks its section in the release notes. `.github/workflows/label-by-title.yml` labels each
78
+ PR when it is opened or its title edited: `feat`/`perf` → `enhancement` (🚀 Features), `fix` → `bug` (🐛 Fixes),
79
+ `docs` → `documentation` (📖 Documentation), `build`/`ci`/`test`/`chore`/`refactor`/`style` → `skip-changelog`
80
+ (left out). Any other type lands under Other. A label set by hand stays until the title is next edited. Keep the
81
+ workflow and `.github/release.yml` naming the same labels.
82
+ - One logical change per commit.
83
+ - No `Co-Authored-By` lines. No "Generated with Claude Code" attribution.
84
+ - Never commit directly to `main`. Every change goes through a PR.
85
+ - PR descriptions: no checkboxes. "Tested" lists what actually ran, not what was planned.
86
+ - No hand-written CHANGELOG; GitHub Releases carry generated notes.
87
+
88
+ ### Before opening a PR
89
+
90
+ ```bash
91
+ npm run format # prettier --write + eslint --fix
92
+ npm run lint
93
+ npm test
94
+ npm run build:config # only when src/configpanel/ changed; commit the output
95
+ ```
96
+
97
+ CI (`.github/workflows/ci.yml`) calls signalk-server's shared `plugin-ci.yml`: install, `npm test`, plugin lifecycle
98
+ and schema checks, npm-pack contents, and an App Store style `--ignore-scripts` install. It does **not** run Prettier or
99
+ ESLint, so run them locally.
100
+
101
+ ### Releases
102
+
103
+ Releases are cut by release-please. Every releasable push to `main` updates a standing release PR titled
104
+ `chore(release): X.Y.Z` that bumps `version` in `package.json` and `package-lock.json`. Merging it creates the tag and
105
+ the GitHub Release, and `.github/workflows/release-please.yml` then dispatches `release_on_tag.yml` on the tag, which
106
+ publishes to npm with provenance (OIDC trusted publishing, no token).
107
+
108
+ - **The version follows the commits:** `feat` → minor, `fix`, `perf` and `revert` → patch, `!` or a
109
+ `BREAKING CHANGE:` footer → major. A `Release-As: X.Y.Z` footer overrides it. Do not bump the version in an ordinary
110
+ PR.
111
+ - **A push with nothing releasable leaves the release PR alone.** The `gate` job in `release-please.yml` decides what
112
+ counts; its comment lists the cases. Revert with a conventional `revert:` subject, since release-please ignores
113
+ GitHub's `Revert "…"`. Change the gate's last alternative together with `pull-request-title-pattern` in
114
+ `release-please-config.json`, or the release PR's merge never creates a tag.
115
+ - **Merge the release PR once it lists your change.** release-please refreshes it a moment after each merge; a
116
+ release PR merged before that still ships the change (the tag is on top of it) but its notes leave it out.
117
+ - **Pre-releases are cut by hand:** a `chore(release): X.Y.Z-beta.N` PR, then an annotated tag `vX.Y.Z-beta.N` pushed
118
+ on its merge commit. `release_on_tag.yml` creates their Release itself and publishes under the npm dist-tag `alpha`,
119
+ `beta` or `rc` that the tag contains.
120
+
121
+ ## File layout
122
+
123
+ | Path | Purpose |
124
+ | ----------------------------- | ------------------------------------------------------------------------------------- |
125
+ | `plugin/index.js` | Plugin entry: schema, default-app seeding, routes, night-mode PUT handler. |
126
+ | `public/index.html` | Dock page: idle screen, dock, backdrop, loading overlay, welcome tour. |
127
+ | `public/dock.js` | Dock logic: access detection, config loading and polling, iframes, double-tap, dock. |
128
+ | `public/dock.css` | Dock styling, magnification, positions. |
129
+ | `public/manifest.webmanifest` | PWA manifest for Add to Home Screen. |
130
+ | `public/config.html` | Older standalone configurator. |
131
+ | `src/configpanel/` | React config panel for the admin UI (Module Federation remote). |
132
+ | `webpack.config.js` | Builds `src/configpanel/` into `public/`; shares React as a singleton with the admin. |
133
+ | `test/plugin.test.js` | Plugin tests (`node:test`). |
134
+ | `docs/screenshots/` | App Store screenshots, listed in `package.json` under `signalk.screenshots`. |
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalk/app-dock",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "macOS-style configurable app dock for switching between SignalK webapps on touch screens",
5
5
  "repository": {
6
6
  "url": "https://github.com/SignalK/app-dock"
Binary file
@@ -5,10 +5,8 @@
5
5
  <stop offset="100%" stop-color="#2563eb"/>
6
6
  </linearGradient>
7
7
  </defs>
8
- <!-- White base -->
9
- <rect width="72" height="72" fill="#ffffff"/>
10
- <!-- Background -->
11
- <rect width="72" height="72" rx="16" fill="url(#bg)"/>
8
+ <!-- Background, full bleed: whatever displays the icon rounds the corners -->
9
+ <rect width="72" height="72" fill="url(#bg)"/>
12
10
  <!-- Dock pill -->
13
11
  <rect x="8" y="48" width="56" height="16" rx="8" fill="#ffffff" fill-opacity="0.25" stroke="#ffffff" stroke-opacity="0.4" stroke-width="0.5"/>
14
12
  <!-- Dock icons - 3 squares in the pill -->
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
+ "release-type": "node",
4
+ "include-component-in-tag": false,
5
+ "changelog-type": "github",
6
+ "skip-changelog": true,
7
+ "pull-request-title-pattern": "chore(release): ${version}",
8
+ "packages": {
9
+ ".": {}
10
+ }
11
+ }