Skip to content

Add a Plugins tab with import and Documents-backed storage#21

Merged
colemancda merged 6 commits into
masterfrom
feature/plugins-tab
Jul 20, 2026
Merged

Add a Plugins tab with import and Documents-backed storage#21
colemancda merged 6 commits into
masterfrom
feature/plugins-tab

Conversation

@colemancda

Copy link
Copy Markdown
Member

Makes plugins a first-class, user-manageable part of the app: a top-level tab, import from a file,
and on-disk storage in Documents that bundled plugins are installed into on first launch.

Follows #20, which added the GATT characteristic plugins this manages.

Storage

Every plugin — bundled or imported — now lives at Documents/Plugins/<plugin-id>/, holding its
manifest and module. One location means one load path and one place a user can actually look.

  • First launch copies the bundled plugins in. A new app build shipping a changed module hash
    refreshes them.
  • A bundled plugin you delete stays deleted. The install record remembers it was already
    handled, so it is not resurrected on the next launch.
  • Import validates before it writes. The module is parsed and its ABI marker, capability exports
    and sha256 are all checked before anything lands on disk, so a bad import cannot leave a
    broken plugin behind.
  • Enable/disable now persists across launches.

If Documents is unavailable the manager falls back to loading read-only from the app bundle, so
parsing keeps working even when management does not.

UI

Plugins is now a top-level tab rather than a row buried in Settings (the now-redundant Settings link
is removed). The screen gains an Import button, swipe-to-delete, and inline reporting of import and
load errors.

Tests

44 pass, up from 37. The seven new ones cover the storage logic directly: first-launch install,
that installed plugins load and verify against their own hashes, idempotence on a second launch,
that a deleted bundled plugin is not resurrected, that a valid import copies and loads, that an
import with a tampered hash fails and writes nothing, and identifier-to-folder-name sanitising.

They run against temporary directories, never the real Documents folder — worth noting because
PluginDirectory.default() resolves to ~/Documents on a macOS test host, so a careless test here
would write plugins into the developer's own home directory.

Reviewer notes

  • iOS-focused, as requested. .fileImporter and security-scoped resource access are gated
    behind #if !os(Android) / #if canImport(Darwin). Android compiles but has no import button
    yet; the storage layer itself is portable.
  • Skip's bridging plugin rejects private @State, so PluginsView's state properties are internal
    with a comment saying why — otherwise the build fails with "cannot be bridged to Android".
  • Built and tested against a machine-local reconstruction of the dependency graph; a clean checkout
    still cannot resolve. Pre-existing and unrelated — see Documentation/DependencyState.md on
    feature/skip-dependency-updates.

@colemancda
colemancda merged commit a17188b into master Jul 20, 2026
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