Skip to content

Image widget backed by the standalone SK Image plugin - #1120

Open
dillan wants to merge 11 commits into
mxtommy:masterfrom
dillan:feat/image-widget-sk-image
Open

Image widget backed by the standalone SK Image plugin#1120
dillan wants to merge 11 commits into
mxtommy:masterfrom
dillan:feat/image-widget-sk-image

Conversation

@dillan

@dillan dillan commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Adds an Image display widget to KIP. Image storage and processing now live in the standalone SK Image Signal K plugin (published, in the App Store); KIP ships only the widget, a shared-library picker, and a Settings → Media cache card, all talking to the plugin's REST API.

This supersedes the earlier three-PR split (#1080 server, #1081 client, #1082 config UI): the server engine became the standalone sk-image plugin, so there's nothing server-side left to land in KIP. Those three can be closed in favor of this one.

What's in it

  • Image widget — displays a server-stored image, scaled to fit/fill while preserving aspect ratio, over a solid or transparent background. Lazy-loaded like the other widgets.
  • Shared-library picker in the widget config — upload (≤10 MB, type-guarded), pick from the boat-wide library, delete with confirm, alt text, fit/background, live preview.
  • Settings → Media card — image cache size + purge.
  • ImageAssetService + a plugin-URL resolver that target the plugin's crew-reachable /signalk/v1/api/sk-image mount and discover the variant-width list from the plugin's GET /config.
  • sk-image added to signalk.recommends so the App Store offers to install it when KIP is installed/updated.

Notable correctness details

  • Targets the crew-reachable mount, not the admin-gated alias. On a secured server the server admin-gates every /plugins/* route, so a /plugins/sk-image path would 401/403 ordinary crew — and the native <img>, which sends no auth header — even for reads. The widget uses /signalk/v1/api/sk-image, where reads are public and only writes need a read-write/admin principal.
  • Non-admin crew are not dead-ended when adding the widget: the plugin-state API is admin-only, so an unreadable state is treated as "can't verify" rather than "not installed".
  • Read-only accounts get a clear 403 message ("your account is read-only") instead of a network error.
  • No full-res fetch on first paint — the widget requests the smallest variant until it's measured, then upgrades.

Requirements

  • The SK Image plugin (1.5.0+) on the server, which requires Node.js 22.13+.

Tests

Unit tests cover the mount resolution (incl. a /signalk-suffixed configured URL and a host literally named signalk), the auth-aware dependency check, the read-only 403 copy, and the first-paint variant. Full suite green (492), lint clean, production build + plugin build OK.

Screenshot 2026-07-06 at 8 44 00 PM Screenshot 2026-07-06 at 8 44 35 PM

dillan added 10 commits July 6, 2026 09:05
Port the Image display widget, the shared-library picker in widget config,
and the Settings -> Media image-cache card onto the current master. Image
storage, processing, and caching now live in the standalone SK Image Signal K
plugin (available in the app store); the widget talks to its REST API through
ImageAssetService.

Declare sk-image in signalk.recommends so the Signal K App Store offers to
install it when KIP is installed or updated. Registered the widget with a lazy
component loader to match the current widget.service structure.
These tests intentionally fail. They pin the Image widget to the plugin's
crew-reachable /signalk/v1/api/sk-image mount instead of the /plugins/sk-image
alias. On a secured Signal K server the server admin-gates every /plugins/*
route, so the alias 401/403s ordinary crew (and the native <img> element, which
sends no auth header) even for reads — making the widget unusable for its
intended audience. Also covers a configured URL that already carries a /signalk
suffix, which must not double up. The resolver fix follows.
Resolve the plugin base against the server's /signalk/v1/api/<id> mount instead
of the /plugins/<id> alias. signalk-server admin-gates every /plugins/* route on
a secured server, so the alias returned 401/403 to ordinary crew — and to the
native <img> element, which sends no auth header — even for reads, leaving the
widget unusable for its intended audience. The /signalk/v1/api mount is public
for reads and only gates writes on a read-write/admin principal.

Add a shared stripToServerRoot() helper and use it in both resolvePluginBaseUrl
and PluginConfigClientService.toServerUrl, so a configured URL that already ends
in /signalk (which the connection service accepts) no longer produces a doubled
or misrooted path for image and plugin-admin requests.
The add-widget dependency check treated any failed plugin-state lookup as
"installed but disabled" and prompted to enable it. On a secured server the
/plugins/{id} state API is admin-only, so ordinary crew got a 401/403, were told
the required plugin was disabled, and were dead-ended with an "enable" prompt
they have no permission to satisfy.

Now an auth-required/forbidden result is treated as "can't verify" and does not
block the add: the widget's own endpoints are crew-reachable and render (or
surface an install hint) on their own. Installed-but-disabled and not-found keep
their existing prompt behavior.
On a secured server a logged-in read-only account can view the library but not
change it. A denied upload/delete now returns 403, so tell the user their
account is read-only and to ask an admin for read-write access, instead of the
old "check your connection" message that blamed the network for a permissions
problem.

Also point the "library won't load" 404 at installing the SK Image plugin from
the App Store, and make the viewer's unavailable message say the image may have
been removed or the server is unreachable.
The Image widget starts with a container width of 0, and an unknown width snaps
to the largest (2560px) variant — so every image widget fetched a full-res copy
on first paint, then replaced it once the real width was measured. Request the
smallest variant until the first measurement instead; it's cheap and upgrades on
resize.

Make the asset service's base URL and discovered width allow-list signals, so a
URL built inside a reactive computed (the widget) recomputes when the server's
GET /config list arrives instead of locking to the built-in fallback.
The Image widget's help doc and changelog described image serving as if it were
built into KIP. Say plainly that images are stored and served by the separate
SK Image Signal K plugin (1.4.0+), that KIP recommends it so the App Store offers
to install it, and that the plugin needs Node.js 24+ on the server. Clarify that
changing images needs write access (a read-only account can view but not edit).

Also make the help doc's image paths base-relative (assets/help-docs/img/...)
instead of parent-relative, per the mxtommy#1056 guard added upstream.
The harness described the pre-split, integrated model — build one kip.tgz that
bundles the image server, and read images from /plugins/kip/images. That
architecture no longer exists.

Install the standalone sk-image plugin from npm alongside the KIP webapp, seed
and read the library at /signalk/v1/api/sk-image, enable sk-image in the baked
config, and note the Node 24+ requirement. Document how to verify the
secured-server role behavior (anon/read-only can view, read-only gets 403 on
upload, admin manages) — that multi-role check is manual; only the open
screenshot server is scripted here.
stripToServerRoot ran a chain of ../signalk suffix replacements over the whole
URL, so a server addressed as http://signalk (e.g. behind a reverse proxy on
port 80) collapsed to http:/ — breaking every plugin and image request. Parse
off the scheme+host first and strip the /signalk[/vN[/api]] segment from the
path only, so the authority is never touched. Adds tests for the signalk-host
and reverse-proxy-subpath cases.

Also spell out that a missing library can be installed OR enabled, since a
disabled plugin 404s the same as an uninstalled one.
SK Image 1.5.0 lowered its Node floor to 22.13. Point the help doc, changelog,
and screenshot harness at 1.5.0+ and Node.js 22.13+, and soften the harness
Dockerfile note since most signalk-server images already run Node 22+.
…k-image

# Conflicts:
#	CHANGELOG.md
#	package.json
@dillan
dillan marked this pull request as ready for review July 7, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant