|
|
|
|
@@ -0,0 +1,235 @@
|
|
|
|
|
---
|
|
|
|
|
phase: quick-261016-s8x
|
|
|
|
|
plan: 01
|
|
|
|
|
type: execute
|
|
|
|
|
wave: 1
|
|
|
|
|
depends_on: []
|
|
|
|
|
files_modified:
|
|
|
|
|
- modules/pact/capabilities.go
|
|
|
|
|
- modules/pact/README.md
|
|
|
|
|
- modules/cabana/navigation.go
|
|
|
|
|
- modules/cabana/permissions_test.go
|
|
|
|
|
- modules/cabana/README.md
|
|
|
|
|
- docs/backend/users-and-permissions.md
|
|
|
|
|
- docs/backend/admin-controllers.md
|
|
|
|
|
- admin/src/styles/main.css
|
|
|
|
|
- modules/boardwalk/dist/
|
|
|
|
|
- ../sm-paymentgateway-plugin/admin_navigation.go
|
|
|
|
|
- ../sm-paymentgateway-plugin/admin_navigation_test.go
|
|
|
|
|
- ../sm-paymentgateway-plugin/controllers/quotes/_quotes_notice.htm
|
|
|
|
|
- ../sm-paymentgateway-plugin/controllers/quoteables/_quoteables_notice.htm
|
|
|
|
|
- ../sm-paymentgateway-plugin/controllers/shippings/_shippings_notice.htm
|
|
|
|
|
- ../sm-paymentgateway-plugin/controllers/orders/_orders_stats.htm
|
|
|
|
|
- ../sm-paymentgateway-plugin/controllers/payments/_payments_stats.htm
|
|
|
|
|
- ../sm-paymentgateway-plugin/README.md
|
|
|
|
|
autonomous: true
|
|
|
|
|
requirements: [QUICK-261016-s8x]
|
|
|
|
|
|
|
|
|
|
estimate:
|
|
|
|
|
tokens: 70000
|
|
|
|
|
raw_tokens: 70000
|
|
|
|
|
tasks: 3
|
|
|
|
|
confidence: low
|
|
|
|
|
|
|
|
|
|
must_haves:
|
|
|
|
|
truths:
|
|
|
|
|
- 'With quotes turned off in Settings -> Payment gateway, GET <prefix>/api/v1/navigation no longer lists Quotes and Quote items under "Payments & orders"; with orders turned off it lists neither Orders, Quotes, Quote items nor Shipping methods and the "Payments & orders" item opens Payments; with only shipping turned off just Shipping methods is gone. Payments is always listed. Turning a feature back on shows its items on the next navigation load (SPA boot or sign-in), with no restart.'
|
|
|
|
|
- 'A screen whose feature is off, opened by its URL, still answers its list with the "Turned off" callout above it, exactly as before.'
|
|
|
|
|
- 'In a header partial a callout followed by a stats strip has 12px of space below it, and a header partial with no stats renders no empty rounded strip: the five payment gateway header templates emit the summer-stats <dl> only when there is at least one stat. Field notices in forms (FormField.vue) and the order and payment hint partials look as before.'
|
|
|
|
|
- 'pact.NavigationItem has a Visible func(ctx context.Context) bool field; nil keeps the entry always listed. cabana.Registry.Metadata runs it with the request context after the permission check, leaves out a main item (with its whole side menu) or a side item whose Visible answers false, and a main item whose Controller is the controller of a side item hidden this way links to its first visible openable side item (empty when there is none). Registry validation, the NavigationEntry JSON shape and the OpenAPI document are unchanged.'
|
|
|
|
|
- 'go vet ./..., go test -short ./modules/pact ./modules/cabana, go test ./cmd/summer -run TestDocsTree, go run ./cmd/summer docs:build --check and scripts/check-admin-dist.sh pass in summercms.go; go vet ./... and go test -short ./... pass in sm-paymentgateway-plugin. modules/boardwalk/dist is rebuilt and committed. Nothing is tagged, pushed or deployed, no application submodule pointer is bumped, every commit is staged by explicit path and carries no co-author tag.'
|
|
|
|
|
artifacts:
|
|
|
|
|
- path: "modules/pact/capabilities.go"
|
|
|
|
|
provides: "the Visible hook on a navigation entry"
|
|
|
|
|
contains: "Visible func(ctx context.Context) bool"
|
|
|
|
|
- path: "modules/cabana/navigation.go"
|
|
|
|
|
provides: "Metadata skips hidden entries and repoints a parent whose controller is a hidden child's"
|
|
|
|
|
contains: "Visible"
|
|
|
|
|
- path: "modules/cabana/permissions_test.go"
|
|
|
|
|
provides: "short unit test of the Visible rules next to TestNavigationDropsDeniedParentAndRepointsTarget"
|
|
|
|
|
contains: "func TestNavigationHidesInvisibleItemsAndRepointsTarget"
|
|
|
|
|
- path: "admin/src/styles/main.css"
|
|
|
|
|
provides: "space below a callout that a sibling follows inside a partial tree"
|
|
|
|
|
contains: ".summer-partial .summer-callout:not(:last-child)"
|
|
|
|
|
- path: "../sm-paymentgateway-plugin/admin_navigation.go"
|
|
|
|
|
provides: "Visible closures on orders, quotes, quoteables and shippings reading the settings per request"
|
|
|
|
|
contains: "Visible:"
|
|
|
|
|
- path: "../sm-paymentgateway-plugin/admin_navigation_test.go"
|
|
|
|
|
provides: "short test of the visibility rules and of the no-app fallback"
|
|
|
|
|
contains: "func TestNavigationVisibility"
|
|
|
|
|
- path: "../sm-paymentgateway-plugin/controllers/quoteables/_quoteables_notice.htm"
|
|
|
|
|
provides: "the stats strip only when there are stats (same change in the other four header templates)"
|
|
|
|
|
contains: "{{- if .Stats -}}"
|
|
|
|
|
- path: "../sm-paymentgateway-plugin/README.md"
|
|
|
|
|
provides: "the navigation paragraph describes request-time visibility instead of always-listed items"
|
|
|
|
|
contains: "Visible"
|
|
|
|
|
key_links:
|
|
|
|
|
- from: "../sm-paymentgateway-plugin/admin_navigation.go (Visible closures)"
|
|
|
|
|
to: "modules/cabana/navigation.go (Metadata)"
|
|
|
|
|
via: "cabana calls item.Visible(ctx) on every GET /navigation; the closure loads models.Settings through the booted app and answers the feature flag, true on any fallback"
|
|
|
|
|
pattern: "Visible"
|
|
|
|
|
- from: "modules/cabana/navigation.go (openableTarget)"
|
|
|
|
|
to: "the set of side-item controllers hidden by Visible"
|
|
|
|
|
via: "a parent whose Controller is in that set links to the first visible openable child, so Payments & orders opens Payments while orders are off"
|
|
|
|
|
pattern: "openableTarget"
|
|
|
|
|
- from: "the five header templates"
|
|
|
|
|
to: "controllers statsView.Stats"
|
|
|
|
|
via: "the <dl class=\"summer-stats\"> block is wrapped in an if on .Stats"
|
|
|
|
|
pattern: "if .Stats"
|
|
|
|
|
- from: "admin/src/styles/main.css"
|
|
|
|
|
to: "PartialHost.vue wrapper div.summer-partial"
|
|
|
|
|
via: "the rule is scoped to .summer-partial so FormField.vue's callout is untouched"
|
|
|
|
|
pattern: "summer-partial .summer-callout"
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
<objective>
|
|
|
|
|
Two admin fixes for the payment gateway plugin, reported from the "Quote items" screen while quotes are turned off:
|
|
|
|
|
|
|
|
|
|
1. The "Turned off" callout above a list has no space below it and an empty rounded strip (the stats `<dl>` with no stats) sits between it and the list card.
|
|
|
|
|
2. Screens whose feature is off in Settings -> Payment gateway (Orders, Quotes, Quote items, Shipping methods) still appear in the admin side menu. They must disappear from the navigation; opening one by URL keeps showing the callout.
|
|
|
|
|
|
|
|
|
|
The framework has no request-time navigation visibility today (`pact.NavigationItem` is static and `cabana.Registry.Metadata` filters by permission only), so this adds one additive, non-breaking hook (`pact.NavigationItem.Visible`), uses it in the plugin, fixes the templates and adds the callout spacing in the admin CSS.
|
|
|
|
|
|
|
|
|
|
Purpose: the admin shows menu entries for features the shop has turned off, and the feature-off screen looks broken.
|
|
|
|
|
Output: two commits in `summercms.go` (framework hook with README/docs and a unit test; CSS fix) plus a dist rebuild commit, and one commit in `../sm-paymentgateway-plugin` (navigation, templates, README, test). No tag, push, deploy or submodule pointer bump.
|
|
|
|
|
|
|
|
|
|
Hard limits: other Claude sessions work in `summercms.go`: stage by explicit path, never `git add -A`; do not touch `.planning/STATE.md`, `.planning/state.json`, `.msd/` or `.planning/phases/17-blog-companions/17-VERIFICATION.md`. No co-author tags. One logical change per commit. `sm-paymentgateway-plugin` is a core plugin: no breaking change to its exported API (every exported identifier listed in its README line 338 stays). Framework code and docs never name a consuming application.
|
|
|
|
|
</objective>
|
|
|
|
|
|
|
|
|
|
<execution_context>
|
|
|
|
|
@~/.claude/msd-core/workflows/execute-plan.md
|
|
|
|
|
@~/.claude/msd-core/templates/summary.md
|
|
|
|
|
</execution_context>
|
|
|
|
|
|
|
|
|
|
<context>
|
|
|
|
|
@CLAUDE.md
|
|
|
|
|
@modules/pact/capabilities.go
|
|
|
|
|
@modules/cabana/navigation.go
|
|
|
|
|
@modules/cabana/permissions_test.go
|
|
|
|
|
@admin/src/styles/main.css
|
|
|
|
|
@../sm-paymentgateway-plugin/admin_navigation.go
|
|
|
|
|
@../sm-paymentgateway-plugin/admin.go
|
|
|
|
|
@../sm-paymentgateway-plugin/controllers/orders_partials.go
|
|
|
|
|
|
|
|
|
|
## Planning-time observations (2026-10-16, live)
|
|
|
|
|
|
|
|
|
|
Edit scope rests on these facts; re-check the ones a task's precondition names before editing.
|
|
|
|
|
|
|
|
|
|
- `summercms.go` working tree: only `.planning/STATE.md`, `.planning/state.json` (modified), `.msd/` and `.planning/phases/17-blog-companions/17-VERIFICATION.md` (untracked), all other sessions' files. Nothing uncommitted under `modules/`, `admin/` or `docs/`. `../sm-paymentgateway-plugin` is clean on its own repo (`go.work` replaces `git.golem15.com/golem15/summercms` with `../summercms.go`; its `go.mod` still requires a tagged framework, so the plugin can only bump that requirement after a framework release that contains this hook).
|
|
|
|
|
- Baselines pass today: `go vet ./modules/pact ./modules/cabana && go test -short ./modules/pact ./modules/cabana` in the framework (cabana short suite 0.9 s); `go vet ./... && go test -short ./...` in the plugin. `summer` is NOT on PATH: use `go run ./cmd/summer docs:build --check`. `admin/node_modules` is present (no `npm ci` needed). Docker is available but nothing here needs it.
|
|
|
|
|
- `pact.NavigationItem` (`modules/pact/capabilities.go:541`): `Code, Label, Icon, Permissions, Order, Controller, SideMenu`. `pact` already imports `context`. `pact.SettingsItem` carries func fields tagged `json:"-"` (`NewModel`), the convention to follow for a func field.
|
|
|
|
|
- `cabana.Registry.Metadata(ctx, principal, tr)` (`modules/cabana/navigation.go:39`): drops a parent the principal cannot open, filters children by `Allows`, then `view.Controller = r.openableTarget(principal, item, children)`; `openableTarget` (line 101) keeps `item.Controller` when empty or `canOpen`, else the first child the principal can open, else "". The items come from `r.navigation`, collected once in `registry.go:313-320` (`validateNavigationShape`) and validated in `validateNavigation` (line ~405-445): codes, duplicate children, known controllers, known permissions. Neither looks at any other field, so a func field needs no validation change. `GET /navigation` is served from `http.go:498` with the request context.
|
|
|
|
|
- The SPA loads navigation once per boot and after sign-in (`admin/src/state/useNavigation.ts` `loadNavigation`, called from `main.ts:30` and `LoginView.vue:33`); `railEntries` omits a plugin whose side menu is empty. `NavigationEntry` (cabana) is the JSON shape and does not change, so swag/OpenAPI are untouched.
|
|
|
|
|
- Existing test to model on: `TestNavigationDropsDeniedParentAndRepointsTarget` (`modules/cabana/permissions_test.go:70`) builds `&Registry{byID: ..., navigation: ...}` directly with the `navController` helper (same file, line 62) and calls `reg.Metadata(context.Background(), principal, nil)`.
|
|
|
|
|
- Docs that describe navigation and must stay truthful (`docs:build --check` validates every `pkg.Identifier` and `pkg.Type.Field` in prose): `modules/pact/README.md` line 27 (Features bullet) and line 122 (API table row); `modules/cabana/README.md` line 58 (Features bullet "Backend navigation ... filtered per user by `cabana.Registry.Metadata`") and the routes table row `GET /navigation, GET /settings` (line ~83); `docs/backend/users-and-permissions.md` line 67 (the paragraph after the Navigation example: "...navigation and settings entries are filtered by the permissions they name... links to its first openable side-menu entry instead"); `docs/backend/admin-controllers.md` "Compilation at boot" paragraph (line ~159: "compiles every plugin's controllers, settings, navigation and permissions once... Nothing is parsed per request"). The Go examples in those pages are `src=` copies of `modules/cabana/example_controller_test.go`: do not edit them (prose only), so `docs:sync` is not needed. `docs/plugins/registration.md` line 84 only lists the interfaces and stays true.
|
|
|
|
|
- Plugin navigation (`../sm-paymentgateway-plugin/admin_navigation.go`): `Navigation()` lists five side items always and picks the top-level `Controller` at boot via `ordersEnabledAtBoot()` (app nil, no `*gorm.DB`, settings table missing, load error: all answer true). `p.application()` (`admin.go:34`) returns the booted `*backpack.App` (nil before Register). `Navigation()` is called once by `cabana.Activate` at boot; a closure stored in the item runs later, at request time, when the app is set. Settings: `models.LoadSettings(ctx, db)` returns `*models.Settings` with `OrdersEnabled`, `ShippingsEnabled`, `UseQuotes` (`models/settings.go:34-36`), `TableName()` `golem15_paymentgateway_settings`.
|
|
|
|
|
- Feature-off rules used by the callouts today (keep the navigation identical to them): orders off when `!OrdersEnabled` (`controllers/orders_partials.go:126`); quotes and quote items off when `!OrdersEnabled || !UseQuotes` (`controllers/quoteables.go:73 quotesNotice`); shippings off when `!OrdersEnabled || !ShippingsEnabled` (`controllers/shippings.go:86`). Payments is never off.
|
|
|
|
|
- Header templates (`controllers/<name>/_<name>_notice.htm` for quotes, quoteables, shippings; `_orders_stats.htm`; `_payments_stats.htm`): each renders the callout inside `{{- with .Notice -}}` and then ALWAYS `<dl class="summer-stats">` ranging over `.Stats` (`statsView{Notice *noticeView; Stats []statView}`, `orders_partials.go:52`). An empty `<dl>` is the empty strip from the screenshot. No plugin test pins the `<dl>`.
|
|
|
|
|
- CSS: `.summer-callout { margin: 0; ... }` (`admin/src/styles/main.css:459`), `.summer-stats { margin: 0; ... }` (line 349), `.summer-partial` rules at line 214. `PartialHost.vue:79` wraps every rendered partial in `<div class="summer-partial" data-partial="header|hint|...">`; `ListView.vue:444` is `<section class="flex flex-col gap-5">`, so the partial wrapper already has 20px to the list card and the only missing space is inside the partial, between the callout and the strip. `.summer-callout` is also used by `admin/src/components/form/FormField.vue:71` (a field notice between the comment and the error list, outside any `.summer-partial`) and by the plugin's `_order_hint.htm` and `_payment_hint.htm` (callout is the last child of the hint). A rule scoped to `.summer-partial` and to a callout that a sibling follows touches none of them.
|
|
|
|
|
- Plugin tests are all `package paymentgateway_test`; Go allows an internal `package paymentgateway` test file next to them, which is what a pure unexported visibility function needs. The plugin's short admin tests live in `admin_short_test.go`.
|
|
|
|
|
- Commit naming precedent for quick tasks in both repos: `feat(quick-261016-nqk): ...`, `fix(quick-...)`, `chore(quick-261016-nqk): rebuild embedded admin dist`. The plan and summary are committed by the quick workflow, not by these tasks.
|
|
|
|
|
</context>
|
|
|
|
|
|
|
|
|
|
<tasks>
|
|
|
|
|
|
|
|
|
|
<task type="auto">
|
|
|
|
|
<name>Task 1: Framework hook pact.NavigationItem.Visible, honoured by cabana.Registry.Metadata, with test and docs</name>
|
|
|
|
|
<precondition>In /media/nvme/dev/golem15/summercms.io/summercms/summercms.go `git status --short -- modules admin docs` prints nothing (no other session is editing pact, cabana, the SPA or docs).</precondition>
|
|
|
|
|
<files>modules/pact/capabilities.go, modules/pact/README.md, modules/cabana/navigation.go, modules/cabana/permissions_test.go, modules/cabana/README.md, docs/backend/users-and-permissions.md, docs/backend/admin-controllers.md</files>
|
|
|
|
|
<action>
|
|
|
|
|
Repo: /media/nvme/dev/golem15/summercms.io/summercms/summercms.go. One commit, `feat(quick-261016-s8x): pact.NavigationItem.Visible hides navigation entries at request time`, staged by the seven paths above.
|
|
|
|
|
|
|
|
|
|
pact (`modules/pact/capabilities.go`): add the field `Visible func(ctx context.Context) bool` with the tag `json:"-"` to `NavigationItem`, after `SideMenu`. Doc comment on the field: nil means always listed; when set, cabana calls it on every navigation request with the request context, after the permission check, and leaves the entry out when it answers false; a main item hidden this way takes its side menu with it; it must be cheap, must not panic and should answer true when it cannot decide (no database yet). Extend the type's comment accordingly. pact stays dependency-free (a func field only; `context` is already imported).
|
|
|
|
|
|
|
|
|
|
cabana (`modules/cabana/navigation.go`): in `Metadata`, after `Allows` passes for a main item, skip the item when `item.Visible != nil && !item.Visible(ctx)` (so a denied principal never runs plugin code). While walking `item.SideMenu`, after the child's `Allows` check, skip a child whose Visible answers false and record its `Controller` in a small set of hidden controllers (a local map keyed by controller ID, ignoring ""). Change the unexported `openableTarget` to take that set and to treat `item.Controller` as not usable when it is in the set, falling through to the existing loop over the (already visible and permitted) children, so a parent whose own controller is a hidden child's links to the first visible openable child and to "" when none is. Permission-denied children are not added to the set: the existing permission behaviour (parent keeps its controller when the principal can open it) is unchanged. Update the comments of `Metadata` and `openableTarget`. Do not touch `NavigationEntry`, registry validation, swag annotations or the SPA.
|
|
|
|
|
|
|
|
|
|
Test (`modules/cabana/permissions_test.go`, directly after `TestNavigationDropsDeniedParentAndRepointsTarget`, same `Registry{byID, navigation}` and `navController` pattern, superuser principal so permissions are not the factor): `TestNavigationHidesInvisibleItemsAndRepointsTarget` covering (a) a side item with Visible false is absent from `SideMenu` while a sibling with nil Visible stays; (b) a parent whose Controller is that hidden child's controller gets the first visible openable child as `Controller`; (c) a parent with Visible false is dropped although every child is visible; (d) a parent whose only child is hidden and whose Controller is that child's keeps an empty SideMenu and an empty Controller; (e) Visible receives the context passed to Metadata (a `context.WithValue` key read inside the func). Use neutral names (`acme.shop.*`).
|
|
|
|
|
|
|
|
|
|
Docs, same commit, prose only (the Go examples on these pages are `src=` copies and must not change): `modules/pact/README.md` line 27 bullet (add that a `pact.NavigationItem.Visible` func hides an entry at request time, nil = always listed) and line 122 row ("One backend navigation entry, with an optional side menu and an optional `Visible` func the admin runs on every navigation request."). `modules/cabana/README.md` line 58 bullet: add one sentence that an entry whose `Visible` func answers false for the request is left out like a denied one (a hidden main item with its side menu) and that a main item whose controller is a side item hidden this way links to its first visible openable side item; the `GET /navigation, GET /settings` row: "...the administrator may open and, for a navigation entry with a `Visible` func, that it answers true for the request". `docs/backend/users-and-permissions.md` line 67 paragraph: one sentence after "...links to its first openable side-menu entry instead." stating that a plugin can also hide an entry per request with `pact.NavigationItem.Visible` (for a screen whose feature is turned off in its settings) and that the same drop and relink rules apply. `docs/backend/admin-controllers.md` "Compilation at boot" paragraph: after "Nothing is parsed per request." add that the one run-time hook is `pact.NavigationItem.Visible`, which runs on every `GET /navigation` so a plugin can hide a screen whose feature is off; it must be cheap and answer true when unsure.
|
|
|
|
|
</action>
|
|
|
|
|
<verify>
|
|
|
|
|
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && go vet ./modules/pact ./modules/cabana && go test -short ./modules/pact ./modules/cabana -count=1 && go test -short ./modules/cabana -run 'TestNavigationHidesInvisibleItemsAndRepointsTarget|TestNavigationDropsDeniedParentAndRepointsTarget' -count=1 -v && go test ./cmd/summer -run TestDocsTree -count=1 && go run ./cmd/summer docs:build --check && grep -q 'Visible func(ctx context.Context) bool' modules/pact/capabilities.go && grep -q 'Visible' modules/pact/README.md && grep -q 'Visible' modules/cabana/README.md && grep -q 'pact.NavigationItem.Visible' docs/backend/users-and-permissions.md && grep -q 'pact.NavigationItem.Visible' docs/backend/admin-controllers.md && HEAD_FILES="$(git show --name-only --format= HEAD)" && ! printf '%s\n' "$HEAD_FILES" | grep -q -E '^(admin/openapi|modules/cabana/admin_openapi\.go)' && test -z "$(git status --porcelain -- modules docs)"</automated>
|
|
|
|
|
</verify>
|
|
|
|
|
<done>The new cabana test and the whole pact and cabana short suites pass; the docs checker passes with the new identifiers; the OpenAPI inputs are untouched; the change is committed in summercms.go by explicit paths with the given message and no co-author tag.</done>
|
|
|
|
|
</task>
|
|
|
|
|
|
|
|
|
|
<task type="auto">
|
|
|
|
|
<name>Task 2: Space below a header-partial callout (admin CSS) and the embedded dist rebuild</name>
|
|
|
|
|
<files>admin/src/styles/main.css, modules/boardwalk/dist/</files>
|
|
|
|
|
<action>
|
|
|
|
|
Repo: /media/nvme/dev/golem15/summercms.io/summercms/summercms.go. Two commits: `fix(quick-261016-s8x): space below a callout that a sibling follows in a partial` (main.css only) and then `chore(quick-261016-s8x): rebuild embedded admin dist` (`modules/boardwalk/dist` only), both staged by path.
|
|
|
|
|
|
|
|
|
|
In `admin/src/styles/main.css`, in the Callout block that starts at `.summer-callout` (line ~459, inside the same `@layer` block), add the rule `.summer-partial .summer-callout:not(:last-child) { margin-bottom: 12px; }` with a one-line comment that it spaces a callout from the stats strip (or any sibling) that follows it inside a rendered partial. Keep `.summer-callout { margin: 0 }` as is: the rule must not reach `FormField.vue`'s field notice (outside `.summer-partial`) nor change the order and payment hint partials, whose callout is the last child.
|
|
|
|
|
|
|
|
|
|
Rebuild the embedded SPA with `npm --prefix admin run build` (node_modules is present; run `npm --prefix admin ci` only if `admin/node_modules` is missing) and commit `modules/boardwalk/dist` as the second commit.
|
|
|
|
|
</action>
|
|
|
|
|
<verify>
|
|
|
|
|
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && grep -q '\.summer-partial \.summer-callout:not(:last-child)' admin/src/styles/main.css && grep -c '\.summer-callout {' admin/src/styles/main.css | grep -qx 1 && npm --prefix admin run build && scripts/check-admin-dist.sh && test -z "$(git status --porcelain -- admin modules/boardwalk)"</automated>
|
|
|
|
|
</verify>
|
|
|
|
|
<done>The scoped margin rule exists, the base `.summer-callout` rule is unchanged, `scripts/check-admin-dist.sh` reports that the committed dist matches a fresh build, and both commits exist with no co-author tag.</done>
|
|
|
|
|
</task>
|
|
|
|
|
|
|
|
|
|
<task type="auto">
|
|
|
|
|
<name>Task 3: Plugin uses Visible for the feature-off screens, templates drop the empty stats strip, README and short test</name>
|
|
|
|
|
<precondition>`grep -q 'Visible func(ctx context.Context) bool' /media/nvme/dev/golem15/summercms.io/summercms/summercms.go/modules/pact/capabilities.go` succeeds (Task 1 committed; the plugin's go.work replace points at that checkout) and `git -C /media/nvme/dev/golem15/summercms.io/summercms/sm-paymentgateway-plugin status --short` prints nothing.</precondition>
|
|
|
|
|
<files>../sm-paymentgateway-plugin/admin_navigation.go, ../sm-paymentgateway-plugin/admin_navigation_test.go, ../sm-paymentgateway-plugin/controllers/quotes/_quotes_notice.htm, ../sm-paymentgateway-plugin/controllers/quoteables/_quoteables_notice.htm, ../sm-paymentgateway-plugin/controllers/shippings/_shippings_notice.htm, ../sm-paymentgateway-plugin/controllers/orders/_orders_stats.htm, ../sm-paymentgateway-plugin/controllers/payments/_payments_stats.htm, ../sm-paymentgateway-plugin/README.md</files>
|
|
|
|
|
<action>
|
|
|
|
|
Repo: /media/nvme/dev/golem15/summercms.io/summercms/sm-paymentgateway-plugin (its own git repository). One commit there, `fix(quick-261016-s8x): hide feature-off screens from the navigation and drop the empty stats strip`, staged by the eight paths (relative to that repo). Do not bump any application's submodule pointer, do not change `go.mod`, do not push or tag.
|
|
|
|
|
|
|
|
|
|
`admin_navigation.go`: add a pure unexported function, e.g. `navVisibility(s models.Settings)` returning the three booleans orders, quotes, shippings, with exactly the callout rules: orders = `s.OrdersEnabled`; quotes = `s.OrdersEnabled && s.UseQuotes`; shippings = `s.OrdersEnabled && s.ShippingsEnabled`. Add a method on `*Plugin` that loads the settings for one navigation request, e.g. `navSettings(ctx) (models.Settings, bool)`, with the same fallbacks the boot-time helper has today (nil app, no `*gorm.DB` in `app.Lookup`, settings table missing, load error or nil: not loaded), a 5 s timeout derived from the request context, and no logging. Set `Visible` on the orders, quotes, quoteables and shippings side items to closures that call it and answer true when the settings could not be loaded, else the matching rule; payments gets no Visible. The closures capture `p`, so they see the app that `Register` sets even though `Navigation()` runs at boot. Make the top-level item's `Controller` simply `controllers.OrdersControllerID` (cabana relinks it to Payments while orders are hidden) and delete the boot-time orders check and its now-unused imports, keeping the registered-controller filter. Rewrite the `Navigation` doc comment: the four feature items are listed while their feature is on, read per request, a change shows on the next navigation load without a restart, a screen opened by URL while off still shows the callout.
|
|
|
|
|
|
|
|
|
|
`admin_navigation_test.go` (new, `package paymentgateway`, short, no Docker): `TestNavigationVisibility` with a table over `models.Settings{OrdersEnabled, UseQuotes, ShippingsEnabled}` combinations asserting the three rules (all on: all visible; orders off: all three hidden whatever the others; orders on and quotes off: quotes hidden, shippings visible; orders on and shippings off: shippings hidden, quotes visible), plus a check that `(&Plugin{}).Navigation()` yields one parent whose `Controller` is `controllers.OrdersControllerID`, whose `payments` side item has a nil Visible and whose other four side items have a non-nil Visible that answers true with `context.Background()` (the no-app fallback).
|
|
|
|
|
|
|
|
|
|
The five header templates: wrap the `<dl class="summer-stats">` block, from the `<dl` line through `</dl>`, in `{{- if .Stats -}}` ... `{{- end -}}` so an empty stats list renders nothing after the callout. Keep everything else byte-identical.
|
|
|
|
|
|
|
|
|
|
`README.md`: rewrite the navigation paragraph (line 122, "`Plugin.Navigation` adds ...") so it states that Orders, Quotes, Quote items and Shipping methods are listed only while their feature is on (`orders_enabled`; `orders_enabled` and `use_quotes`; `orders_enabled` and `shippings_enabled`), through a `Visible` func the admin runs on every navigation request, so a change shows on the next navigation load without a restart and every item is listed before the database is published or migrated; a screen opened by URL while its feature is off still shows the "Turned off" callout above its list; the top-level item opens Orders, or Payments while orders are off. Leave the partials table rows (lines 164-174) and the exported-API list (line 338) as they are; `Navigation` stays exported and its signature is unchanged.
|
|
|
|
|
|
|
|
|
|
In the SUMMARY, note that the plugin now needs a framework release containing `pact.NavigationItem.Visible` before its `go.mod` requirement is bumped (development works through `go.work`), and that no application pointer was bumped.
|
|
|
|
|
</action>
|
|
|
|
|
<verify>
|
|
|
|
|
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/sm-paymentgateway-plugin && go vet ./... && go test -short ./... -count=1 && go test -short . -run TestNavigationVisibility -count=1 -v && test "$(grep -l '{{- if .Stats -}}' controllers/quotes/_quotes_notice.htm controllers/quoteables/_quoteables_notice.htm controllers/shippings/_shippings_notice.htm controllers/orders/_orders_stats.htm controllers/payments/_payments_stats.htm | wc -l)" = 5 && grep -c 'Visible:' admin_navigation.go | grep -qx 4 && grep -q 'Visible' README.md && git diff --quiet HEAD -- go.mod go.sum && test -z "$(git status --porcelain)"</automated>
|
|
|
|
|
</verify>
|
|
|
|
|
<done>The plugin's vet and short suites pass with the new visibility test; the four feature items carry Visible and payments does not; all five templates emit the stats strip only when there are stats; the README describes request-time visibility; go.mod is unchanged; the commit exists in the plugin repo with no co-author tag.</done>
|
|
|
|
|
</task>
|
|
|
|
|
|
|
|
|
|
</tasks>
|
|
|
|
|
|
|
|
|
|
<threat_model>
|
|
|
|
|
## Trust Boundaries
|
|
|
|
|
|
|
|
|
|
| Boundary | Description |
|
|
|
|
|
|----------|-------------|
|
|
|
|
|
| browser (SPA) -> GET /navigation | A navigation request now runs plugin code (the Visible funcs) per request, after authentication and the permission check. |
|
|
|
|
|
| plugin Visible func -> database | The payment gateway reads its settings row on every navigation request through the booted app. |
|
|
|
|
|
| admin CSS -> every rendered partial | A global stylesheet rule reaches every plugin's partial trees. |
|
|
|
|
|
|
|
|
|
|
## STRIDE Threat Register
|
|
|
|
|
|
|
|
|
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
|
|
|
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
|
|
|
|
| T-261016-s8x-01 | Information disclosure | cabana Metadata ordering | medium | mitigate | Visible runs only after `Allows` passes for the item (and for the parent), so a denied principal never triggers plugin code and a hidden entry leaks neither its label nor its controller; the new test and the existing permission test both stay green. |
|
|
|
|
|
| T-261016-s8x-02 | Denial of service | plugin Visible on every GET /navigation | low | mitigate | The closure does one settings load with a 5 s timeout from the request context and answers true on any failure (no panic path, no retry); navigation is loaded once per SPA boot, not per screen. |
|
|
|
|
|
| T-261016-s8x-03 | Elevation of privilege | hidden screen opened by URL | low | accept | Visibility is a menu concern: the controller's `RequiredPermissions` still guard every request and the screen keeps answering with the feature-off callout, exactly as before. |
|
|
|
|
|
| T-261016-s8x-04 | Tampering | the margin rule reaching FormField.vue or the hint partials | low | mitigate | The rule is scoped to `.summer-partial` and to a callout that a sibling follows; FormField's notice is outside a partial and the hint callouts are last children; `check-admin-dist.sh` proves the committed dist is the one built from this CSS. |
|
|
|
|
|
| T-261016-s8x-SC | Tampering | npm/Go dependency installs | low | accept | No new dependency in either repo; `npm --prefix admin run build` uses the installed node_modules from the committed lockfile; the plugin's go.mod is unchanged. No package-legitimacy checkpoint is needed. |
|
|
|
|
|
</threat_model>
|
|
|
|
|
|
|
|
|
|
<verification>
|
|
|
|
|
Framework (`summercms.go`): `go vet ./... && go test -short ./... -count=1 && go test ./cmd/summer -run TestDocsTree -count=1 && go run ./cmd/summer docs:build --check && scripts/check-admin-dist.sh`, then `test -z "$(git status --porcelain -- modules admin docs)"` (the other sessions' `.planning`/`.msd` files are expected to remain).
|
|
|
|
|
|
|
|
|
|
Plugin (`../sm-paymentgateway-plugin`): `go vet ./... && go test -short ./... -count=1 && test -z "$(git status --porcelain)"`.
|
|
|
|
|
|
|
|
|
|
Commit hygiene in both repos: `git log -5 --format=%B | grep -ci 'co-authored-by'` prints 0; every new commit message starts with `feat(quick-261016-s8x)`, `fix(quick-261016-s8x)` or `chore(quick-261016-s8x)`; no tag was created (`git tag --points-at HEAD` empty in both repos); no application repository was touched.
|
|
|
|
|
</verification>
|
|
|
|
|
|
|
|
|
|
<success_criteria>
|
|
|
|
|
- With quotes off, `GET /navigation` for a superuser lists Orders, Payments and Shipping methods only under "Payments & orders"; with orders off it lists Payments only and the parent's `controller` is `golem15.paymentgateway.payments`; with everything on all five are listed and the parent opens Orders (proved by the pure-function test plus the cabana relink test; a live check against a running app is optional).
|
|
|
|
|
- Opening a hidden screen by URL still returns its list with the "Turned off" callout.
|
|
|
|
|
- A header partial with a callout and no stats renders the callout alone; with stats, the strip starts 12px below the callout.
|
|
|
|
|
- `pact.NavigationItem.Visible` exists, is documented in the pact and cabana READMEs and the two docs pages, and the docs checker passes.
|
|
|
|
|
- All gates in `<verification>` pass; the embedded dist is committed; nothing is pushed, tagged, deployed or pointer-bumped; no co-author tags.
|
|
|
|
|
</success_criteria>
|
|
|
|
|
|
|
|
|
|
<output>
|
|
|
|
|
Create `.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/261016-s8x-SUMMARY.md` when done, listing the commits in both repositories and the framework-release note for the plugin's go.mod.
|
|
|
|
|
</output>
|