From 450c53545285dafc712d9458fa4c5edddba76352 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 16 Oct 2026 20:41:55 +0200 Subject: [PATCH] docs(quick-261016-s8x): PaymentGateway: banner bottom margin and hide disabled sections from admin navigation --- .planning/STATE.md | 1 + .../261016-s8x-PLAN.md | 235 ++++++++++++++++++ .../261016-s8x-SUMMARY.md | 166 +++++++++++++ .../deferred-items.md | 6 + 4 files changed, 408 insertions(+) create mode 100644 .planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/261016-s8x-PLAN.md create mode 100644 .planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/261016-s8x-SUMMARY.md create mode 100644 .planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/deferred-items.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 1edc5476..1fd26234 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -900,6 +900,7 @@ Recent decisions affecting current work: | 261015-w7f | Framework docs since v0.1.14 + Upgrading to 0.2, release v0.2.0 (plugins, layers), deploy summercms.io | 2026-10-15 | a204d38 | [261015-w7f-update-framework-docs-for-phase-19-3-rel](./quick/261015-w7f-update-framework-docs-for-phase-19-3-rel/) | | 36 | summercms.io copy Alpha 0.1 → 0.2 (vue b5d6dbb, app 6a13a87), deployed | 2026-10-15 | 853ea1f | — | | 261016-0fm | Admin minors for v0.2.1: theme switch, language switch, settings nav on form pages | 2026-10-16 | 66fd4ae | [261016-0fm-admin-minors-for-v0-2-1-theme-switch-lan](./quick/261016-0fm-admin-minors-for-v0-2-1-theme-switch-lan/) | +| 261016-s8x | PaymentGateway: banner bottom margin and hide disabled sections from admin navigation | 2026-10-16 | ab9f385 | [261016-s8x-paymentgateway-banner-bottom-margin-and-](./quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/) | ## Deferred Items diff --git a/.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/261016-s8x-PLAN.md b/.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/261016-s8x-PLAN.md new file mode 100644 index 00000000..9c449612 --- /dev/null +++ b/.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/261016-s8x-PLAN.md @@ -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 /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
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
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" +--- + + +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 `
` 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. + + + +@~/.claude/msd-core/workflows/execute-plan.md +@~/.claude/msd-core/templates/summary.md + + + +@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//__notice.htm` for quotes, quoteables, shippings; `_orders_stats.htm`; `_payments_stats.htm`): each renders the callout inside `{{- with .Notice -}}` and then ALWAYS `
` ranging over `.Stats` (`statsView{Notice *noticeView; Stats []statView}`, `orders_partials.go:52`). An empty `
` is the empty strip from the screenshot. No plugin test pins the `
`. +- 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 `
`; `ListView.vue:444` is `
`, 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. + + + + + + Task 1: Framework hook pact.NavigationItem.Visible, honoured by cabana.Registry.Metadata, with test and docs + 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). + 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 + +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. + + + 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)" + + 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. + + + + Task 2: Space below a header-partial callout (admin CSS) and the embedded dist rebuild + admin/src/styles/main.css, modules/boardwalk/dist/ + +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. + + + 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)" + + 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. + + + + Task 3: Plugin uses Visible for the feature-off screens, templates drop the empty stats strip, README and short test + `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. + ../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 + +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 `
` block, from the ``, 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. + + + 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)" + + 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. + + + + + +## 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. | + + + +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. + + + +- 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 `` pass; the embedded dist is committed; nothing is pushed, tagged, deployed or pointer-bumped; no co-author tags. + + + +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. + diff --git a/.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/261016-s8x-SUMMARY.md b/.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/261016-s8x-SUMMARY.md new file mode 100644 index 00000000..46b356df --- /dev/null +++ b/.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/261016-s8x-SUMMARY.md @@ -0,0 +1,166 @@ +--- +phase: quick-261016-s8x +plan: 01 +subsystem: admin navigation, payment gateway admin screens +tags: [cabana, pact, navigation, visibility, admin-css, paymentgateway] +status: complete +requirements: [QUICK-261016-s8x] + +dependency_graph: + requires: + - cabana.Registry.Metadata permission filtering (WR-02) + - PartialHost.vue wrapper div.summer-partial + provides: + - pact.NavigationItem.Visible (request-time navigation visibility hook) + - cabana.Registry.Metadata honouring Visible and relinking a parent whose controller is a hidden child's + - payment gateway side items hidden while their feature is off + - header partials without an empty stats strip + affects: + - every plugin's GET /navigation response (only when a plugin sets Visible) + - sm-paymentgateway-plugin go.mod (needs a framework release that contains the hook before its requirement is bumped) + +tech_stack: + added: [] + patterns: + - func field tagged json:"-" on a pact registration struct (as pact.SettingsItem.NewModel) + - plugin closures capture *Plugin so a boot-time Navigation() reads the booted app at request time + - CSS rule scoped to .summer-partial so a global stylesheet change touches only rendered partials + +key_files: + created: + - ../sm-paymentgateway-plugin/admin_navigation_test.go + 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/ (rebuilt) + - ../sm-paymentgateway-plugin/admin_navigation.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 + +decisions: + - "Visible runs after Allows for the item (and after the parent's Allows for a side item), so a denied principal never runs plugin code and a hidden entry leaks nothing (T-261016-s8x-01)." + - "Only Visible-hidden side items are recorded in the hidden set passed to openableTarget; permission-denied children keep the existing WR-02 behaviour (a parent keeps its own controller when the principal can open it)." + - "The plugin's top-level item always declares Orders as its Controller; cabana relinks it to Payments while orders are hidden, so the boot-time ordersEnabledAtBoot check is gone and a settings change needs no restart." + - "navSettings answers 'not loaded' (item listed) on every fallback: nil app, no *gorm.DB, settings table absent, load error; the load uses a 5 s timeout derived from the request context (T-261016-s8x-02)." + - "The callout spacing is a scoped rule (.summer-partial .summer-callout:not(:last-child)) rather than a change to .summer-callout, so FormField.vue's field notice and the hint partials are untouched (T-261016-s8x-04)." + +metrics: + duration: ~25 min + completed: 2026-10-16 + tasks: 3 + framework_commits: 3 + plugin_commits: 1 + +actuals: + tokens: 10545 + tasks: 3 + commits: 3 +plan_head_before: 90b2d4e23bb9e70e59ea50fac66eef67377939c2 +plan_head_after: ab9f385db8afdf94769e400a9b121fff7db3b16d +plugin_head_before: (clean main before 16484ba) +plugin_head_after: 16484ba7f51be1ecdb6f7384a88bdf3fa6df2ef7 +--- + +# Quick 261016-s8x: Payment gateway banner bottom margin and feature-off screens hidden from the navigation Summary + +Added `pact.NavigationItem.Visible`, a per-request visibility hook honoured by `cabana.Registry.Metadata` (drop a hidden main item with its side menu or a hidden side item, relink a parent whose controller is a hidden child's to its first visible openable child), used it in the payment gateway so Orders, Quotes, Quote items and Shipping methods disappear from the admin menu while their feature is off in Settings -> Payment gateway, made the five header partials emit the `summer-stats` strip only when there are stats, and gave a callout 12px of space below it when a sibling follows it inside a rendered partial. + +## Commits + +### summercms.go (framework, branch `master`) + +| Hash | Message | Files | +| --- | --- | --- | +| `06e2e60` | `feat(quick-261016-s8x): pact.NavigationItem.Visible hides navigation entries at request time` | 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 | +| `2dd5fb3` | `fix(quick-261016-s8x): space below a callout that a sibling follows in a partial` | admin/src/styles/main.css | +| `ab9f385` | `chore(quick-261016-s8x): rebuild embedded admin dist` | modules/boardwalk/dist/ (index.html, renamed index/editor JS chunks, new CSS asset) | + +Measured: `git rev-list --count 90b2d4e..ab9f385` = 3. + +### sm-paymentgateway-plugin (own repo, branch `main`) + +| Hash | Message | Files | +| --- | --- | --- | +| `16484ba` | `fix(quick-261016-s8x): hide feature-off screens from the navigation and drop the empty stats strip` | admin_navigation.go, admin_navigation_test.go, 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, README.md | + +No tag, push, deploy or application submodule pointer bump in either repo. No co-author trailer in any commit (`git log -5 --format=%B | grep -ci co-authored-by` = 0 in both). + +## Framework release note for the plugin + +The plugin now compiles only against a framework that has `pact.NavigationItem.Visible`. Development works through the plugin's `go.work` replace (`../summercms.go`); its `go.mod` was left unchanged (still requires the last tagged framework) and must be bumped only after a framework release containing commit `06e2e60` is tagged. No application pointer was bumped. + +## What was built + +### Task 1: framework hook (`06e2e60`) + +- `pact.NavigationItem.Visible func(ctx context.Context) bool` (tag `json:"-"`), nil = always listed; field and type doc comments describe the contract (cheap, no panic, true when unsure, runs after the permission check, a hidden main item takes its side menu). +- `cabana.Registry.Metadata`: skips a main item whose Visible answers false (after `Allows`); while walking the side menu skips a child whose Visible answers false after its `Allows` check and records its controller in a local `hidden` set; `openableTarget` now takes that set and treats `item.Controller` as unusable when it is in it, so the parent links to the first visible openable child, or "" when none. +- `TestNavigationHidesInvisibleItemsAndRepointsTarget` covers: hidden side item absent while a nil-Visible sibling stays; parent repointed to the first visible openable child; parent with Visible false dropped although its child is visible; parent whose only child is hidden ends with empty SideMenu and empty Controller; Visible receives the context passed to Metadata. +- Prose-only docs: pact README (Features bullet, API row), cabana README (Features bullet, `GET /navigation` route row), `docs/backend/users-and-permissions.md`, `docs/backend/admin-controllers.md`. The `src=` Go examples were not touched; `NavigationEntry`, registry validation, swag/OpenAPI untouched. + +### Task 2: callout spacing (`2dd5fb3`, `ab9f385`) + +- `.summer-partial .summer-callout:not(:last-child) { margin-bottom: 12px; }` next to the base `.summer-callout { margin: 0 }` rule (unchanged, still the only `.summer-callout {` rule). +- `npm --prefix admin run build` and the rebuilt `modules/boardwalk/dist` committed; `scripts/check-admin-dist.sh`: "modules/boardwalk/dist matches a fresh build". + +### Task 3: plugin (`16484ba`) + +- `navVisibility(models.Settings) (orders, quotes, shippings bool)`: `OrdersEnabled`; `OrdersEnabled && UseQuotes`; `OrdersEnabled && ShippingsEnabled` (the callout rules). +- `(*Plugin).navSettings(ctx) (models.Settings, bool)`: booted app -> `app.Lookup[*gorm.DB]` -> `HasTable` -> `models.LoadSettings`, 5 s timeout from the request context, no logging, false on every fallback. +- `Visible` closures on orders, quotes, quoteables and shippings (true when settings cannot be loaded); payments has none; top-level `Controller` is `controllers.OrdersControllerID`; `ordersEnabledAtBoot` deleted; the registered-controller filter kept; `Navigation` doc comment rewritten. +- The five header templates wrap the `
` block in `{{- if .Stats -}}` ... `{{- end -}}`; everything else byte-identical. +- `admin_navigation_test.go` (`package paymentgateway`, short): `TestNavigationVisibility` with a six-case table over the settings flags and a shape check of `(&Plugin{}).Navigation()` (one parent opening Orders, payments nil Visible, the other four non-nil and true with `context.Background()`). +- README navigation paragraph rewritten to request-time visibility; the partials table rows and the exported-API list are unchanged; `Navigation` stays exported with the same signature (no breaking change to this core plugin). + +## Verification (real output) + +Framework (`summercms.go`): + +- `go vet ./... && go test -short ./... -count=1`: exit 0, every package `ok`. +- `go test -short ./modules/cabana -run 'TestNavigationHidesInvisibleItemsAndRepointsTarget|TestNavigationDropsDeniedParentAndRepointsTarget' -v`: both PASS. +- `go test ./cmd/summer -run TestDocsTree -count=1`: ok. +- `go run ./cmd/summer docs:build --check`: `docs:build: no problems found`. +- `scripts/check-admin-dist.sh`: `check-admin-dist: modules/boardwalk/dist matches a fresh build`. +- `git show --name-only HEAD` of `06e2e60` touches neither `admin/openapi` nor `modules/cabana/admin_openapi.go`. +- `git status --porcelain -- modules admin docs`: empty (the other sessions' `.planning/STATE.md`, `.planning/state.json`, `.msd/`, `17-VERIFICATION.md` remain untouched and unstaged). + +Plugin (`sm-paymentgateway-plugin`): + +- `go vet ./... && go test -short ./... -count=1`: exit 0, every package `ok`. +- `go test -short . -run TestNavigationVisibility -v`: PASS with 6 subtests. +- Extra, beyond the plan: `go test . -run TestOrdersAdminSmoke -count=1` (Docker/testcontainers Postgres, not short): ok in 5.5 s. Its `navigation` subtest fetches `GET /navigation` through the booted app with `orders_enabled`, `shippings_enabled` and `use_quotes` on and still sees Orders, Quotes and Quote items for an `access_orders` admin, so the `Visible` closures and `navSettings` work against a live database. +- 5 templates contain `{{- if .Stats -}}`; `grep -c 'Visible:' admin_navigation.go` = 4; README mentions `Visible`; `git diff --quiet HEAD -- go.mod go.sum`: unchanged; `git status --porcelain`: empty; `git tag --points-at HEAD`: empty. + +## Deviations from Plan + +None - plan executed exactly as written. (The Docker-backed smoke run above is an extra check, not a change.) + +## Deferred Issues + +Pre-existing, out of scope, logged in `deferred-items.md` next to this file: `gofmt -l` reports `modules/cabana/account_tokens.go`, `account_tokens_test.go`, `token_postgres_smoke_test.go`, `token_routes_test.go`, `token_scopes_test.go` (framework) and `mail_smoke_test.go` (plugin) as unformatted. None was touched. + +## Known Stubs + +None. + +## Threat Flags + +None beyond the plan's register: the only new run-time surface is the `Visible` call on `GET /navigation`, which the plan's T-261016-s8x-01/02 cover (runs after the permission check; one bounded settings load that answers true on failure). + +## State notes + +`.planning/STATE.md` and `.planning/state.json` were not modified: both carry other sessions' uncommitted edits and the task constraints say to leave them alone. ROADMAP.md was not touched (quick task). Docs artifacts (this SUMMARY, PLAN.md, deferred-items.md) are left uncommitted for the orchestrator's docs commit. + +## Self-Check: PASSED + +All listed files exist and commits 06e2e60, 2dd5fb3, ab9f385 (summercms.go) and 16484ba (sm-paymentgateway-plugin) are in history. diff --git a/.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/deferred-items.md b/.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/deferred-items.md new file mode 100644 index 00000000..3b1f48ca --- /dev/null +++ b/.planning/quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/deferred-items.md @@ -0,0 +1,6 @@ +# Deferred items (quick 261016-s8x) + +Out of scope, pre-existing, not touched by this task: + +- `gofmt -l modules/cabana` lists `account_tokens.go`, `account_tokens_test.go`, `token_postgres_smoke_test.go`, `token_routes_test.go`, `token_scopes_test.go` as unformatted (framework). +- `gofmt -l .` in sm-paymentgateway-plugin lists `mail_smoke_test.go` as unformatted.