Skip to content

feat(plugins): add tenant plugin platform and Projects Gantt - #3931

Merged
Findeton merged 7 commits into
ovcsfrom
feat/meta-14274-projects/ovcs
Oct 18, 2026
Merged

Findeton merged 7 commits into
ovcsfrom
feat/meta-14274-projects/ovcs

Conversation

@Findeton

@Findeton Findeton commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Parent issue: https://github.com/sequentech/meta/issues/14274

Adds a tenant plugin platform with Projects as its first bundled plugin. Tenant administrators can enable it in Settings → Plugins, then create independent project timelines, edit tasks beneath the timeline, and import or export CSV plans.

The backend exposes versioned ports for records, history, and time. Harvest derives tenant and actor from the verified token, checks each operation's permission and the tenant's enablement, and scopes storage to that tenant and plugin. Enabling provisions realm permissions before committing; disabling retains records, roles, and history. Three new tables remain untracked by Hasura and are accessed through three authenticated actions. Planning operations do not modify election schedules or state. The gateway checks each operation's permission, or tenant-write for a state change, before contacting any backend, and every plugin change is also an AdminChange entry in the tenant's audit log through audit::act_for and the audit_change trigger.

The Admin Portal loads Projects as a lazy contribution after Templates. The timeline fills the content width, supports keyboard, pointer, and touch interaction, and keeps details/editing below it. The implementation includes project timezones, milestones, task versions and explicit conflict recovery, unsaved-edit navigation guards, read-only access, archive/restore, and history that retains deleted tasks. CSV imports preview row errors and atomically create a new project; exports round-trip safely through spreadsheets. All portal translations and developer/admin documentation are included.

Validation ran in the isolated Felix3 worktree:

Check Result
Plugin domain, CSV, and authorization tests Passed
PostgreSQL integration contracts 6 passed: isolation, lifecycle/role failures, conflicts/archive/delete, retained history, atomic import rollback, and election-table/tenant-deletion behavior
Harvest test binary, against a migrated PostgreSQL database 586 passed, including the guarded-route inventory, route-permission cases, audited-route check, and gateway route tests with audit entries
sequent-core realm permission tests 19 passed
Real Hasura action transport fixture 7 checks passed, including JWT forwarding and conflict/error propagation
Focused frontend Jest tests 28 passed
Chromium stories: every plugin widget's section, Settings, and CustomLayout All passed; stories:inventory --check exits 0
Phone checks at 390px with real touch input 15 passed; desktop captures also inspected at 1440px
Formatting (including hasura/ Prettier), ESLint, REUSE Passed; REUSE exits 0
Committed sequent-core WASM package Regenerated with step-dev wasm --release-package; --check-package exits 0
Clippy with warnings enabled Exit 0; no findings in changed Rust files
Story TypeScript project Passed

Browser checks render the actual portal components with schema-validated GraphQL fixtures. Regression coverage includes rapid task moves, pending writes, focus preservation, unsaved navigation, timezone gaps/ambiguities, and phone overflow.

The full Admin Portal Jest run passes 2,275 of 2,282 tests. The same seven failures reproduce on the untouched base in six suites: buildEnvironment, ImportScreen, signingSettings, ImportFileSelection, Password, and transmissionPackage. The main TypeScript check reports the same 511 errors in the same files on the base with this fresh dependency install, including unbuilt workspace packages; this change adds none.

The migration, Hasura metadata, Harvest, and portal changes ship together. Bundled plugins start disabled for each tenant; enabling Projects is a tenant administrator action.

CI follow-ups: 286ff90fa7 regenerates the committed WASM package, formats the Hasura action types, logs plugin changes in the admin audit, authorizes before any backend, and gives every plugin widget its Storybook section; 929563dbc2 adds the gateway route tests that keep Harvest coverage from decreasing and updates the production journeys for the plugin query, the appended Settings tab, and the plugin route. At 929563dbc2, all 107 checks pass (3 skipped), including the Backend E2E journeys.

Add a versioned, tenant-scoped plugin platform and its first plugin,
Projects, for planning tenant work on a timeline.

Backend:
- sequent-plugin-api: the versioned contract (Manifest, Plugin, scoped
  RecordStore/Audit/Clock ports, stable errors, HistoryScope).
- sequent-plugin-host: compiled-in registry, per-operation permissions,
  lifecycle with role provisioning before commit, PostgreSQL adapter bound
  to the verified tenant and plugin, optimistic versions, append-only
  history with the parent at write time.
- sequent-plugin-projects: projects and tasks, RFC 4180 CSV preview,
  atomic import and spreadsheet-safe export, project and task history
  that keeps deleted tasks.
- Harvest gateway (/plugins, /plugins/state, /plugins/execute) behind
  three Hasura actions; plugin tables are not tracked by Hasura.
- projects-read/write/manage permissions in sequent-core, the Admin Portal
  and the default tenant realm.

Admin Portal:
- Plugin host: provider, lazy routes, menu items after Templates, a
  Settings > Plugins tab appended after the existing tabs.
- Projects: list, create, import and export; a full-width timeline with
  hover and focus previews; details and editing below the timeline;
  keyboard, pointer and touch moves; unsaved-edit, conflict and
  in-flight-write handling; viewer read-only mode.
- A data router so screens can hold navigation with unsaved changes.
- Translations in all portal languages, stories and tests.

Docs for plugin authors and tenant administrators.

Parent issue: sequentech/meta#14274

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129Cs8ygVE7UH6N1XzXk8WP
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review: 2
  • release/10.0
  • feat/meta-12767/.*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: sequentech/step/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 29087df8-1157-4a67-ab7d-76c880618b05

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-18 23:06 UTC

Findeton and others added 6 commits October 17, 2026 21:53
- Regenerate the committed sequent-core WASM package and its yarn.lock
  entries with `step-dev wasm --release-package`: it embeds the realm
  template and permission sources this branch changed.
- Format hasura/metadata/actions.graphql with the hasura package's
  Prettier.
- Log plugin changes in the tenant's admin audit log: the gateway names
  the admin with audit::act_for and the plugin tables carry the
  audit_change trigger.
- Check the operation's permission, or tenant-write, before the gateway
  contacts any backend, and pin both plugin routes in the guarded-route
  inventory and route-permission cases.
- Give every plugin widget its Storybook section (PluginRoute, PluginMenu,
  PluginSettings, Projects, ProjectView, Timeline, TaskEditor, History,
  ImportProject); exclude the non-visual provider, guard and app root
  with their reasons.
- The import dialog's file chooser is a real button for a labelled,
  hidden file input.

Parent issue: sequentech/meta#14274

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129Cs8ygVE7UH6N1XzXk8WP
- Route tests for the plugin gateway against the migrated test database:
  turning Projects on provisions its roles in Keycloak and commits, every
  change is an AdminChange entry of the tenant's log, a Keycloak failure
  leaves Projects off and unlogged, and operations are refused while off,
  versioned, and refused without their permission.
- The production journeys answer TenantPlugins with Projects turned off,
  expect the Settings > Plugins tab after Previews, and smoke-test the
  plugin route.

Parent issue: sequentech/meta#14274

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129Cs8ygVE7UH6N1XzXk8WP
…ng tenant-write

The card printed a literal "API" and "· admin" although plugins.api and
plugins.adminGroup are translated in every language, and administrators
without tenant-write saw a disabled button with no reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129Cs8ygVE7UH6N1XzXk8WP
The portal compiles to ES5, where an Error subclass loses its prototype, so
`instanceof PluginRequestError` was always false in production: a stale save
showed the raw message instead of the conflict view with "Load their version",
and every coded plugin error became generic. Storybook compiles natively,
which hid it. Found in the real-stack acceptance run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129Cs8ygVE7UH6N1XzXk8WP
…roject in the URL

Both found by the real-stack acceptance run, where tokens carry Keycloak user
IDs and the portal is reloaded:

- History, "Last change" and the conflict notice showed the actor's user ID.
  Records and history now also store the username at the time of the change
  (updated_by_name, actor_name), taken from the verified token; the stable ID
  stays the actor. The columns join the plugin migration, which has not been
  released.
- The open project lived only in component state, so a reload or a link
  returned to the list. Under the plugin route it is now
  /plugins/projects/<id>. Discarding changes to go back waits for the
  discarded draft to render, so the navigation guard does not ask twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129Cs8ygVE7UH6N1XzXk8WP
Screen, RoutedProjects and LocalProjects only choose between the list and a
project; the Projects and PluginRoute stories render them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129Cs8ygVE7UH6N1XzXk8WP
@Findeton
Findeton merged commit 00e3f8b into ovcs Oct 18, 2026
168 of 172 checks passed
@Findeton
Findeton deleted the feat/meta-14274-projects/ovcs branch October 18, 2026 23:00
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