Skip to content

Enhancement: Court Planner + Recommendations workflow (Planner, Court Report, Herald Script, Recs Manager, notifications, pass-to-local)#490

Open
baltinerdist wants to merge 128 commits into
amtgard:masterfrom
baltinerdist:feature/court-planner
Open

Enhancement: Court Planner + Recommendations workflow (Planner, Court Report, Herald Script, Recs Manager, notifications, pass-to-local)#490
baltinerdist wants to merge 128 commits into
amtgard:masterfrom
baltinerdist:feature/court-planner

Conversation

@baltinerdist

Copy link
Copy Markdown
Contributor

Court Planner + Recommendations workflow

A complete workflow for planning royal courts and managing award recommendations.

Court Planner

  • Plan kingdom/park courts: schedule, add awards + contributing artisans, drag-reorder, scroll/regalia tracking, and grant directly from the plan
  • Court Report (public): per-kingdom/park court history with awards given
  • Herald script generation for reading court

Recommendations Manager

  • Dedicated management page, server-side paginated (500-row batches + infinite scroll), grouping parallel recommendations into one row per (recipient, award, rank) cluster
  • Server-side filter/sort; Park and Rank columns; CSV export of the full filtered set
  • Grant Award modal — pre-filled from the recommendation, with rank pills (green = already held), officer quick-pick chips (Monarch/Regent), and a park/kingdom/event location picker
  • Snooze-to-next-monarchy, pass-to-local delegation, and second/support a recommendation
  • Anonymous recommendations (giver masked from non-admins)
  • In-app notifications to advocates when a recommendation is granted

Migrations

Seven migrations under db-migrations/ (court tables, recommendation snooze / pass-to-local columns, notification table, etc.). Apply before deploy.

Notes

  • Architecture: DB work lives in system/lib/ork3/ (Court + Report libs); controllers stay thin.
  • Verified locally: PHP lint clean, plus end-to-end browser + DB checks on grant / snooze / export / pagination.

🤖 Generated with Claude Code

@baltinerdist

Copy link
Copy Markdown
Contributor Author

Updated with the Court Planner grant-safety + workflow/UX implementation (commit 748e3df6):

Records integrity

  • Single idempotent grant sink (Court::commitStagedAward) — court-line identity is the idempotency key; Player::AddAward now returns the real inserted id (date-heuristic removed); throw-safe finalize (revert-on-throw, re-runnable).
  • Server-side cross-path reconcile so a Recs-Manager grant can't be re-granted at finalize; update_court_status rejects complete; remove/skip/setStatus guard given rows.

Concurrency

  • ork_court_award.row_version optimistic lock (stale → non-destructive reload), full-field heartbeat reconcile, presence roster + honest sync indicator.

Workflow / mobile / a11y

  • Run-mode "Grant" + auto-finalize on Complete; walk-on adds while published; inline un-skip; mobile stacked-card layout so Grant/Skip are reachable on a phone; a11y additive pass; native confirm/alert removed.

Ceremony polish

  • WCAG-AA rank-pill ramp; functional status (Cancelled → Skipped); expanded-row highlight; flags/type tooltips; ad-hoc add split into Add Award / Add Title with typeable autocompletes grouped exactly like the player Add Award modal (rank pills replacing the number box).

Verified end-to-end against a live instance (6/6 functional + 4/4 fix re-tests; all data-integrity SQL invariants at 0). Migrations: 2026-07-11-court-stage-finalize.sql, 2026-07-12-court-grant-safety.sql.

🤖 Generated with Claude Code

Avery Krouse and others added 29 commits July 18, 2026 16:24
- New Court class, Court controller, and CourtAjax controller
- Court detail and list templates
- DB migration for court planner table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mendations

- Court Planner tab added to Kingdomnew profile (visible to canManage users only):
  loads CourtList/UpcomingEvents from controller, inline card list with status
  badges and award counts, Plan a Court modal using kn-* CSS classes
- Anonymous toggle on all recommendation modals (Playernew, Kingdomnew, Parknew):
  checkbox stores mask_giver=1 in ork_recommendations; submitter fields are
  nulled in PlayerAwardRecommendations for non-ORK-admin callers; ORK admins
  see name with (anon) label; CallerUid + CallerIsOrkAdmin wired through all
  three controllers and the Park/Kingdom/Player AJAX handlers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Court Planner tab on Kingdom and Park profiles (Monarch/Regent/PM access)
- Court detail page: create/manage courts, add awards, reorder, track scroll/regalia status
- Award recommendations: anonymous rec support, snooze functionality, rec enhancements
- Print court script: two-column table layout with rec reason and recommender attribution
- Fix: Court detail template path (HTTP_TEMPLATE → relative path)
- Fix: Print script overlay moved to body level so print CSS selector works correctly
- Fix: Authorization switch fall-through missing break for AUTH_EDIT case
- Fix: Password reset email copy and expiry duration
- DB migrations: snooze-to-recommendations, court-rec-enhancements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Award row layout (Court_detail.tpl):
- Replaced rigid 6-column CSS grid with 2-line flexbox layout
- Line 1: bold persona + park abbreviation + note icon
- Line 2: award name + rank + compact icon-only flags + tracking icons
- Removed type badge (left border color already communicates title/ladder/award)
- Pass-to-Local and From-Rec flags reduced to 20px circle icons with tooltips
- Updated both PHP server-rendered markup and JS cpAppendAwardRow() to match

Plan a New Court modal (Kingdomnew_index.tpl, Parknew_index.tpl, revised.css):
- Fixed modal rendering outside overlay (missing position:fixed CSS)
- Aligned field classes to match existing modal pattern (kn-acct-field, kn-modal-close-btn)
- Added scoped CSS for both kingdom and park new-court modals

revised.js syntax fix:
- Repaired two unclosed IIFE/addEventListener blocks at end of file that prevented
  the court planner tab from responding to clicks

DB migration (local only):
- Created ork_court, ork_court_award, ork_court_award_artisan tables

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reverse-engineered PRD covering: ork_court / ork_court_award /
ork_court_award_artisan tables, recommendation snooze (regnum-scoped
auto-expiry), award context preservation (court_award_id +
source_reason on ork_awards), anonymous recs (mask_giver wired
end-to-end), planning + run-of-show + post-court workflows. Flags the
parallel ork_recommendation_support vs Mask branch's
ork_recommendation_seconds collision that needs resolution before
either can merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Spreadsheet layout for the Order of Court list — sticky column header,
  CSS-grid rows, density toggle (Cozy / Comfortable / Compact) persisted
  to localStorage, sidebar collapse rail, row #'s kept in sync on
  reorder/add/remove.
- Comprehensive dark-mode coverage on both Court_detail and Court_list:
  status bar, sidebar cards, modals, type chips, tracking icons, status
  badges, recommendation modal, expand area, autocomplete dropdowns.
  Inline-styled pills/buttons refactored to classes so theme overrides
  actually apply.
- "Add from Recommendations" modal now delegates to
  Reports::PlayerAwardRecommendations, gaining Master-peerage cascade,
  custom-award carve-out, award_id cross-check, snooze awareness, age,
  seconds, and anon-rec masking. Modal gets 4 view filters
  (Open / All / Snoozed / Already Has), inline age badges, seconds count,
  on-another-court warning, and per-row "Already Has / Covered by Master"
  context. Each row collapsed from ~5 lines to 2 (header + reason).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Standalone spreadsheet-style tool (Recommendations/manage/{kingdom|park}/{id})
consolidating admin rec actions (Grant Now, Dismiss, Snooze, Add to Court) off
the inline profile tab. Reuses existing AJAX endpoints + Court Planner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 bite-sized tasks: route scaffold, getRecommendationCourtMap, controller
data, spreadsheet grid, expand rows, sort/filter, selection+bulk, snooze/
dismiss, Grant Now, Add to Court, inline-tab migration, dark-mode/QA pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Kingdom/Park profile recs tabs: add authority-gated "Manage Recommendations"
button; remove inline grant/snooze/dismiss/add-to-court controls + the now-
orphan add-court overlays (kept the Enter-Awards award modals; community
+1/second controls retained). revised.js handlers left inert (delegated/null-
guarded).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baltinerdist and others added 29 commits July 18, 2026 16:25
…rm absent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… table.dataTable

The Park/Kingdom recommendation tables are not DataTables (no DataTable() init),
so the dark-mode rules scoped to table.dataTable never matched — leaving a white
thead and light :hover background, and dark text on dark bg, in dark mode. Scope
to the always-present .pk-recs-table class instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l bar

Park rendered TWO filter rows: a legacy kn-rec-header-row (style.display filter,
the functional one) and a kn-rec-filter-bar wired to a never-initialized
DataTables path (pkRecDT undefined -> dead no-op). Unlike the lazy-loaded Kingdom
panel, the Park recs table is NOT a DataTable, so the filter-bar's filters did
nothing. Consolidate to one bar mirroring Kingdom's layout: drop the duplicate
header-row filters (keep Recommend in a toolbar, fold Manage into the bar), add
the Snoozed filter, and teach the style.display applyFilter the 'open' filter so
'Open Recs' actually hides already-fulfilled recs. JS change is additive and does
not affect the Kingdom DataTables path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e + dead filters)

The Park recs tab never became a DataTable because (a) the DataTables library
(CSS+JS) was never loaded on the Park page (only Kingdom loads it), so the inline
init threw "Cannot read properties of undefined (reading 'ext')" on
$.fn.dataTable.ext..., and (b) that inline init had unbalanced braces (missing the
if-close and $(function(){...}) close). Both killed the whole <script>, so
pkRecDT/pkRecPrint/pkRecCsv/initEmailSpellCheck never ran and the table never got
the dataTable class — which is what broke dark mode (rules gated on table.dataTable)
and left the filter bar inert.

Fix: load the same DataTables 1.13.8 CSS+JS the Kingdom page loads, and balance the
init braces. Park now matches Kingdom (paging/search/sort, working ext.search
filters, Print/CSV). Drop the Snoozed filter button (Kingdom has none; the
ext.search doesn't handle it) and make the legacy style.display applyFilter stand
down when a recs DataTable is present so it can't fight DataTables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er filter labels

- Manage Recommendations (<a> styled as button) was inheriting the global dark
  link color (#theme_container a -> cyan); override to white for .pk/.kn-manage-recs.
- Shorten 'Below Recommended' -> 'Below Rec'd' and 'At or Above Recommended' ->
  'At or Above Rec'd' on the filter buttons (Park + Kingdom) to save bar space.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PlayerAwardRecommendations selected recs.passed_to_local but the
intermediate row object omitted it, so the PassedToLocal mapping read an
undefined property and was always false — the 'passed to local' badge
never showed on the Recommendations Manager, park, or player recs views
despite the DB flag being set. Carry passed_to_local/_by/_at on the row
object (mirrors the snoozed_* fields).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…anager

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed-on date

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors the Court Planner collapse pattern: header toggle button +
chevron, collapsible body (default open). Uses a chevron glyph swap
(up/down) rather than CSS rotate, which did not take on the FA <i>.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…der/chrome

Wrap the Manager in .rp-root + reports.css and replace the light rm-hero with the
standard reports header (dark indigo bar: icon + title + scope chip + Back) and a
rp-context description strip. Also drop the leftover @media(prefers-color-scheme)
blocks so the grid follows the app's data-theme toggle (was tracking OS setting,
causing header/grid theme mismatch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add one shared component — rank-pill.css `.ladder-rank[data-lvl="1..10"]` — and
use it on every surface that shows ladder ranks: Recommendations Manager, Park &
Kingdom recs tabs, and Court planning (static + JS renders). Rank is clamped to
1..10 (min); non-ladder/0 awards render plain (no pill). Palette is a single cool
spectrum (light blue -> indigo -> violet -> purple -> deep magenta), deliberately
NO green and NO red so rank reads as magnitude, not an approve/reject verdict;
dark-mode tuned for contrast.

Also: in the Recommendations Manager, sorting by Award now subsorts by ladder
rank, following the column's sort direction (data-rank + rmSort tiebreaker).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the blue->magenta spectrum with a wider cool->warm sweep so adjacent
ranks are easier to distinguish: teal, sky, blue, indigo, violet, purple,
magenta, rose, orange, gold. Still deliberately avoids deep green and deep red
(teal/gold/orange are fine); light + dark variants tuned for contrast.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spec for moving the Manager from load-all + client filter/sort to server-side
filter/sort/paginate in 500-row (cluster) batches via infinite scroll, with
eligibility aligned to the recs-tab pills (Open Recs default) and newest-first
default sort.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7-task plan: grouping helper, PlayerAwardRecommendationsPage (cluster paging +
SQL filters/sort, reusing existing hydration for exact flags), shared row
partial, rows() endpoint, first-batch manage(), infinite-scroll client rewrite,
end-to-end verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the rows() action + rmParkMap() helper to Controller_Recommendations
and the recommended_awards_page() model passthrough. The endpoint renders
one 500-row batch of shared _rm_row.tpl partials as JSON. Also fixes a
MariaDB correlation bug in Report::PlayerAwardRecommendationsPage's support
subquery (outer alias not visible through a UNION-in-FROM derived table)
that made every page return zero rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the count SQL

The page/count query approximated AlreadyHas with kacount/awcount only, so the
'Showing N of M' total read a few high (and ator a few low) for ladder recs
covered by a Master peerage. Inject the GetLadderMasterMap ladder->master pairs
as a derived table + correlated EXISTS so the SQL AlreadyHas matches the PHP
hydration exactly. Verified park 76 open now rows==total (272==272).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the plain one-word data-tip on the snooze button with the rich
.rm-passlocal-tip pattern so it can render a bold title plus description:
"Snooze to Next Monarchy" / "Temporarily dismiss this recommendation until
either the Monarch or Regent officer at this level changes." Snoozed state
shows an Unsnooze counterpart. Right-anchored + dark-mode variant; JS swap
targets the icon span so it no longer clobbers the tooltip markup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nt Award modal; + Court/Recs polish

Recommendations Manager
- CSV export endpoint streaming the full filtered/sorted set (Recommendations/export)
- Server-side Rank sort + sortable Rank column header
- Split Park into its own column (abbreviation); Rank into its own column (pill without the "Rank" word)
- Grant Award modal (replaces insta-grant — never grants without confirmation):
  pre-filled recipient/award/rank/note, JSON PlayerAjax grantaward endpoint, court-plan
  reconciliation folded in, resolves the cluster on success
- Grant modal parity with the regular award modal: rank pills (green = already held),
  officer quick-pick chips (Monarch/Regent), "Given at" park/kingdom/event location picker

Court Planner + Recs polish
- Security: add_award scopes KingdomAwardId to the court's kingdom (IDOR)
- grant_award atomic claim (no double-grant); reorder_awards single CASE UPDATE (no N+1)
- All CourtAjax mutations moved into the class.Court lib layer (no raw $DB in the controller)
- controller.Court lookups + hero heraldry routed through the lib (restores cache-buster)
- Court_detail: error handling on post()/cpSaveOrder, native confirm()/alert() → in-product
  dialogs, batch add-from-rec no longer marks failed adds as succeeded
- class.Report: expose HeldRank; class.Court do-while → while (read-after-Next fix)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Design spec for splitting grant (stage) from commit-to-record (finalize),
adding a stored run-vs-plan court mode, a two-tap pre-filled grant modal,
per-row undo, drag-and-drop reorder, a live multi-manager heartbeat, a
prepopulate-skipped-from-last-court banner, and a complete-court modal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDxMDhZUEMepFQ9hMidnpJ
Six-expert review roadmap: S1 idempotent grant sink + QW1-9 + S2 mode-driven
workflow + S3 responsive mobile + S5 optimistic concurrency/presence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXQ2YePHm59VSzozsxbNXJ
…kflow, mobile, collaboration & ceremony polish

Implements the stage/finalize + grant-safety specs on top of the Court Planner.

Grant safety / records integrity:
- Single idempotent commit sink (Court::commitStagedAward); court-line identity is
  the idempotency key. Player::AddAward returns the real inserted id (findRecentAwardId
  date-heuristic removed); throw-safe finalize (revert-on-throw, re-runnable).
- Server-side cross-path reconcile (reconcileGrantForRecommendation, exact rec id OR
  cluster key) so a Recs-Manager grant can't be re-granted at finalize.
- update_court_status rejects 'complete' (only finalize completes a court);
  remove/skip/setStatus guard 'given'; update_award no longer writes lifecycle status.

Concurrency / collaboration:
- ork_court_award.row_version optimistic-lock token (409/stale -> non-destructive
  reload); full-field heartbeat reconcile (add/remove rows); presence roster +
  honest sync indicator; silent background poll.

Workflow / UX / mobile / accessibility:
- Run mode: 'Grant' vocabulary + auto-finalize on Complete; walk-on adds while
  published; inline un-skip. Mobile: overflow-x wrapper + stacked-card layout so
  Grant/Skip are reachable on a phone. a11y additive pass (aria-live, labels,
  contrast, tracker glyphs, dialog roles); native confirm/alert removed.
- Rank-pill contrast ramp reworked to WCAG AA (uniform saturated + white text).
- Functional status (inert manual dropdown removed; Cancelled -> Skipped);
  expanded-row highlight; hero breadcrumb tidy; flags/type instant tooltips.
- Ad-hoc add split into Add Award / Add Title with typeable autocompletes grouped
  exactly like the player Add Award modal (new Model_Award::fetch_award_option_groups),
  rank pills replacing the number box.

Migrations: 2026-07-11-court-stage-finalize.sql, 2026-07-12-court-grant-safety.sql.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXQ2YePHm59VSzozsxbNXJ
@baltinerdist
baltinerdist force-pushed the feature/court-planner branch from 748e3df to d32dc7c Compare July 18, 2026 20:26
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