# Board Members and the Admin Theme — Reports Rendering Issue

**Date:** July 30, 2026 · **updated August 8, 2026** (Option D added; §4 corrected; §10 rewritten)
**Status:** ANALYSIS + RECOMMENDATION. **No code changed. No config changed. No database touched.**
**Decision needed from:** Andrew + Caleb

> ### 📌 August 8, 2026 — read this before §5
>
> A verification pass found that **§4's central claim was wrong**, and it changes the options.
> `user-pages.css:401-478` is *not* dead code. It is a complete, working stylesheet for
> `table.views-table` that never matches only because nothing on the page emits that class.
>
> That creates **Option D** (§5, detailed in §8.2): set the class from the *view* instead of from the
> theme. One config key, no permission grant — and because it makes that stylesheet live, it fixes the
> **CSV download and the table styling together**. §4, §5, §7.4, §9 and §10 are updated below.
>
> **Nothing has been implemented.** A LOCAL trial of Option D was reverted on Aug 8 before import.
> Option D was reached by reading code, not by running it — see §8.3 for what is verified and what is not.
**Reported by:** Andrew, July 30 — Download CSV on the Jersey Report does nothing for a Board Member,
works for Admin.
**Scope:** the four reports at `/admin/ccsoccer/reports`.

---

## TL;DR

Board Members can reach all four reports, but they see them in the **site front-end theme**
(`ccsoccer_theme`) rather than **Claro**, because only the `content_editor` role holds
`view the administration theme`. Two of the four reports are unaffected, one is degraded, one is broken.

The reports were built and tested in Claro. The cheapest and least invasive fix is to put Board Members
in Claro too — a single permission grant, no code — rather than making each report work in two themes.

**Recommendation:** grant `view the administration theme` to the Board Member role. Real cons are listed
in §7; none are blocking, but §7.3 and §7.4 deserve a conscious decision rather than a shrug.

---

## 1. The report from the field

Admin opens `/admin/ccsoccer/reports/jersey-report`, clicks **Download CSV**, gets a file.
Board Member opens the same URL, sees the same table and the same button, clicks it, and nothing happens.
No error, no console message, no download.

Verified on LOCAL by masquerading as `abmeade@hotmail.com` (#90845, Board Member).

## 2. Root cause: one permission, four reports

All four report paths sit under `/admin/`. Core's `AdminRouteSubscriber` automatically marks every HTML
route under that prefix as an admin route:

```php
// web/core/lib/Drupal/Core/EventSubscriber/AdminRouteSubscriber.php:20-22
$path = $route->getPath();
if (($path == '/admin' || str_starts_with($path, '/admin/')) && !$route->hasOption('_admin_route') && static::isHtmlRoute($route)) {
  $route->setOption('_admin_route', TRUE);
```

So the routing file does not need to declare `_admin_route` — it is already true for all four. The **only**
thing deciding whether a user sees Claro on these pages is the `view the administration theme` permission.

A sweep of `config/sync/user.role.*.yml` shows that permission is held by exactly one role:
**`content_editor`**. Not `administrator` (which gets it via `is_admin: true`), not `board_member`, not
`tournament_director`.

`ccsoccer_theme` declares `base theme: false` (`ccsoccer_theme.info.yml:6`), so it inherits templates from
**core modules only** — never from Claro or Classy. That distinction is the whole issue.

## 3. What each report actually does

| Report | Implementation | In Claro | In `ccsoccer_theme` |
|---|---|---|---|
| **City Payment** | `Form/CityPaymentReportForm.php` → `CityReportService` → TCPDF via `PdfService::downloadPdf()` (`PdfService.php:162-170`) | Works | **Works identically.** Three form elements, no `#attached`, no inline JS, no Claro-only classes. The output is a `Response` with `application/pdf` — the theme is never involved. |
| **Insurance** | `Form/InsuranceReportForm.php` → `InsuranceReportService::generateReport()` (`:63-68`) | Works | **Works identically.** Same shape as above. |
| **Tournament Deposits** | `Controller/ReportController::tournamentDeposits()` → `#type => 'table'` (`:397-403`) | Works, styled | **Functions, but degraded.** See §3.1. |
| **Jersey** | View `jersey_report_view`, page display at `admin/ccsoccer/reports/jersey-report` | Works | **CSV download broken.** See §3.2. |

### 3.1 Tournament Deposits — functional, visually bare

Everything *works*: sorting is server-side (`TableSortExtender`, `ReportController.php:209,260`), the
tournament filter is an inline `onchange` (`:393`) with no theme dependency, and the Refund / Forfeit
buttons are styled by `ccsoccer_theme/css/base.css` and the module's `ccsoccer-base.css`. The table itself
renders from core's `system` module template, which every theme gets.

What is lost:

- **No table styling at all.** Neither `ccsoccer_theme/css/*.css` nor the module's `ccsoccer-base.css`
  contains any element-level `table` / `th` / `td` / `thead` rule. Every table rule in the theme is scoped
  to a bespoke class (`base.css:318-322`) or to a Claro-only views class. Result: borderless, unpadded,
  no zebra striping.
- **Sort arrows invisible.** `.tablesort` / `.tablesort--asc` are styled only by
  `core/themes/claro/css/components/tablesort-indicator.css`. Sorting still works; the user just gets no
  visual cue which column is sorted. **This is a genuine usability loss, not cosmetics.**
- `form--inline` on the filter (`:387`) is styled only by Claro and Olivero, so the label and select stack
  vertically instead of sitting on one line.
- `tournament-deposits-table` (`:402`) and `captain-dashboard-nav` (`:373`) are styled **nowhere in the
  repo** — in either theme.

### 3.2 Jersey Report — the CSV button

The Download CSV button is inline JavaScript stored in the view's header area
(`config/sync/views.view.jersey_report_view.yml:477`, text format `full_html`). It scrapes the rendered
table:

```js
const table = document.querySelector('.views-table');
if (!table) return;      // ← silent no-op
```

`views-table` **does not come from the Views module.** It is added only by Claro's template override:

```twig
{# core/themes/claro/templates/views/views-view-table.html.twig:34-42 #}
set classes = ['views-table', 'views-view-table', 'cols-' ~ header|length, …]
```

```twig
{# core/modules/views/templates/views-view-table.html.twig:37-41 — what ccsoccer_theme gets #}
set classes = ['cols-' ~ header|length, responsive ? 'responsive-enabled', sticky ? 'sticky-header']
```

So under `ccsoccer_theme` the table carries only `cols-6 responsive-enabled`, `querySelector` returns
`null`, and the handler returns silently. The button renders and appears clickable, which is why this
reads as "the download link doesn't work" rather than as a permissions problem.

*(Note: the button renders for a Board Member even though that role lacks `use text format full_html`.
`check_markup()` does not gate on the format-use permission. Confirmed empirically on LOCAL. No change
needed there.)*

## 4. Supporting evidence: the codebase already assumes Claro

> **⚠ [CORRECTED Aug 8] The first bullet below used to read "This is dead code inside the front-end
> theme's own stylesheet." That is wrong, and it is the error that hid Option D for nine days.**
> The rules are not dead — they are *unreached*. They are correct, complete, use only defined design
> tokens, and are already loaded on the page. They match nothing solely because no element on it carries
> `.views-table`. Supply the class and the whole block activates. See §8.2.

Two stylesheets are written against classes their own target theme never emits:

- **`ccsoccer_theme/css/user-pages.css:401-478`** styles `.views-table` and `.views-view-table` — Claro-only
  classes. **Not dead code — unreached code.** The block is ~78 lines of finished table styling
  (white card, 1px border, `--radius-xl` corners, `--shadow-sm`, a `--color-gray-100` header band,
  uppercase letterspaced header text, `--space-3`/`--space-4` cell padding, per-row bottom borders with the
  last row's suppressed, and a row hover). **Verified Aug 8:** all 13 custom properties it references are
  defined in `tokens.css`; the file ships in the theme's **`global`** library
  (`ccsoccer_theme.libraries.yml:11`), which `.info.yml` attaches to *every* page the theme renders —
  including `/admin/*` for a Board Member; and `page.html.twig:92` opens `.site-main__inner` with
  `{{ page.content }}` inside it at `:108`, so any report table is a descendant. **Every precondition for
  these rules is already satisfied except the class itself.**
  - One half **is** genuinely dead in both themes and always will be: `.site-main__inner .views-view-table
    table` is a *descendant* selector, but Claro puts `views-view-table` on the `<table>` element itself,
    never on a wrapper. It cannot match anywhere. The live half is the `table.views-table` family.
- **`ccsoccer/insurance-report` library** (`ccsoccer.libraries.yml:413-420` → `css/insurance-report.css`) is
  scoped entirely to `.view-insurance-report`, again a Claro-only class. It is attached by
  `ccsoccer_views_pre_render()` (`ccsoccer.module:2792-2796`) to the `insurance_report` **view**, not to the
  `/admin/ccsoccer/reports/insurance-report` **form** route — so it does not affect Report 3 either way.
  **This one is unconditionally dead** and is not revived by Option D.

Neither is sloppiness. Both are the natural result of building admin tooling while holding the admin theme
permission. But the conclusion originally drawn here — that "make the reports theme-agnostic" means
rewriting every admin-facing stylesheet — **overstated the cost for the Jersey Report specifically.** For
that report the stylesheet was already written. What was missing was three words of view configuration.

**The counter-example is worth crediting:** `js/admin-mobile.js` and `css/admin-mobile.css`
(`ccsoccer.libraries.yml:79-88`) *were* written defensively, with comments naming this exact permission
scenario, and their selector (`main table, .region-content table, .views-element-container table`) works in
both themes. The concern was anticipated once — just not systematically.

## 5. Options considered

| # | Option | Assessment |
|---|---|---|
| **A** | **Grant `view the administration theme` to Board Member** | One permission, no code. Fixes all four reports at once by putting Board Members in the environment the reports were built and tested in. **Recommended.** |
| **B** | Make each report theme-agnostic (fix the CSV selector, add generic table CSS, restyle `form--inline` and `.tablesort` for the front-end theme) | Four separate patches, and per §4 it means rewriting `user-pages.css` and `insurance-report.css` against classes core actually emits. Commits the project to maintaining **two rendering targets forever**, only one of which anyone QAs. Rejected as the primary fix. |
| **C** | Custom theme negotiator forcing Claro on the four report routes only | Narrower than A, but it is new code implementing something core already does via a permission, and it would need extending every time a report is added. Over-engineered for this site. Rejected. |
| **D** | **[NEW Aug 8] Supply `views-table` from the view's Table style, and grant no permission.** One config key on `views.view.jersey_report_view` | Fixes the CSV download **and** the table styling in a single change, because §4's stylesheet is already loaded and only wants the class. No permission change, so no blast radius beyond this one report. Does **not** help Tournament Deposits (§3.1), which is a `#type => 'table'` render array, not a view. Detailed in **§8.2**. |

## 6. Recommendation

**Grant `view the administration theme` to the `board_member` role.**

Rationale:

1. **It is a display permission.** It grants no additional data or action access. Board Members already
   hold `access administration pages` and `access toolbar` — they are already in admin-land, just seeing it
   half-dressed.
2. **It fixes all four reports in one move**, including the two problems (jersey CSV, deposits sort arrows)
   that are functional rather than cosmetic.
3. **It preserves existing work rather than eroding it.** Option B would require reworking CSS that
   currently works correctly in the environment it targets.
4. **It is reversible in one click** — with the caveat in §7.3.

> ### [Aug 8] What Option D does to this recommendation
>
> **The four points above are still true.** What changed is the price of the alternative. This section was
> written believing the only way to fix the Jersey Report without the permission was to *write* new CSS
> (Option B, "four separate patches… two rendering targets forever"). §4 was wrong about that: the CSS
> exists. Option D is one config key, and it fixes the CSV **and** the styling.
>
> **The case for A over D is now narrower, but it is not gone**, and it rests on one thing worth stating
> plainly: **A fixes all four reports; D fixes one.** Specifically, D does nothing for **Tournament
> Deposits** (§3.1) — that is a `#type => 'table'` render array, not a view, so it has no Table style
> to set a class on, and its invisible sort arrows are the *other* genuinely functional loss in this
> document. If the board uses that report, D leaves half the problem standing and a second, different fix
> is needed later.
>
> **The case for D is that it is scoped to the problem actually reported**, costs one line, needs no
> permission change, and — per Andrew, Aug 8 — the Jersey Report is downloaded and printed **once at the
> start of each season**, with no sorting needed because the view already sorts server-side.
>
> **They are not mutually exclusive.** §8 always recommended the class change *alongside* A, for the
> §7.3 reason. A + D remains the most robust combination; D alone is the minimum that closes the original
> report. **This is the decision in front of Andrew and Caleb.**

## 7. Cons — the honest list

### 7.1 It is broader than the four reports

The permission applies to **every** `/admin/*` route a Board Member can reach, not just reports. Given
their current permissions that includes: registration management, the Game Status form, notification node
add/edit, and the `/admin/ccsoccer/*` pages they can access. Nothing becomes *reachable* that was not
before — but the visual change is wider than the problem strictly requires.

### 7.2 Content editing forms will look different

Board Members hold `create notification content` and `edit any notification content`. Node add/edit in
Claro has a different layout from the front-end theme — sidebar meta, vertical tabs, different button
placement. If anyone has learned the current layout, this is retraining. Small, but it is the change most
likely to generate a "where did X go?" message.

### 7.3 It hides the underlying fragility rather than removing it

After this change the CSV button works — **because of a theme permission**. The dependency becomes
invisible-and-satisfied rather than invisible-and-broken, which is arguably worse for discoverability. If
the permission is ever revoked, or a future role gets report access without it, the button breaks again
silently with no signal pointing at the cause. **Mitigation in §8.** This is the con we should consciously
accept rather than overlook.

### 7.4 It entrenches the Claro-only CSS

Once nobody views admin pages in the front-end theme, the dead CSS in §4 stays dead and unnoticed, and the
next piece of admin styling gets written against Claro markup too. The divergence compounds quietly. Not a
reason to reject A — but a reason to log §4 as cleanup rather than closing this thread entirely.

> **[Aug 8] This con inverts under Option D, and the inversion has a sharp edge.**
>
> Under A, §4's CSS stays unreached and eventually gets deleted as cleanup — that is the risk described
> above. Under **D it becomes load-bearing**: `user-pages.css:401-478` turns into the *only* thing giving
> the Jersey Report its header band, cell padding and row dividers for Board Members.
>
> **So D converts a cleanup item into a dependency, and nothing in the code says so.** Anyone tidying
> "dead Claro-scoped CSS" later would silently un-style the report with no error, no console message and
> no failing test — the same silent-no-op shape as the original bug. `OUTSTANDING_ISSUES.md` P8 has been
> flagged accordingly, and §8.2 specifies an in-view comment. **If D is chosen, treat that comment as part
> of the change, not as documentation polish.**

### 7.5 Affordance, not access

A Board Member seeing full Drupal admin chrome may feel more invited to explore admin areas. They cannot
reach anything they could not reach before — permissions are unchanged — but the invitation is different.
Worth a moment's thought given board turnover; not a technical risk.

### 7.6 It is a config change, not code

Unlike the current roster-sync work (code-only, `drush cr`), this touches `user.role.board_member.yml` and
needs a config export/import. See §10.

### 7.7 It does not generalise

If a player- or captain-facing report is ever wanted, this approach does not help — those users will never
have the admin theme, and Option B's work would be needed for real at that point.

## 8. The class change — hardening under A, the whole fix under D

> **[Aug 8] Read §8.2 before treating this as optional.** As written on July 30 this section was hardening
> to be done *alongside* the permission grant. The §4 correction promotes it: on its own it is **Option D**,
> and it delivers the styling as well as the download.

### 8.1 The change itself

Grant the permission **and** decouple the jersey CSV button from the theme. This addresses §7.3 and takes
about ten minutes. Set the view's Table style CSS class so the class the script needs is supplied by the
**view**, not by the theme:

```yaml
# config/sync/views.view.jersey_report_view.yml — currently has no `options` key
      style:
        type: table
        options:
          class: views-table
```

Verified this lands: `ViewsThemeHooks::preprocessViewsViewTable()` (~915-919) copies the Table style's
`class` option into `$variables['attributes']['class']`, which the template merges onto the `<table>`, in
every theme. In Claro the class simply appears twice, which is harmless.

**Add a comment in the view header markup** saying the Download CSV script depends on that setting — it is
a field that looks decorative, and clearing it would silently break the button.

Optionally also scope the selector so neither dependency is load-bearing alone:

```js
const btn = document.getElementById('download-csv');
const table = (btn.closest('[class*="js-view-dom-id-"]') || document).querySelector('table');
if (!table) return;
```

`js-view-dom-id-*` is set by the Views module's own template, so it survives any theme. **Do not** use a
bare `document.querySelector('table')` fallback — that turns a silent no-op into a silent *wrong answer*,
exporting whatever table happens to be first in the DOM.

The view's pager is `type: none`, so all filtered rows render and a DOM scrape captures the complete
result set. Confirmed before endorsing this approach.

### 8.2 [Aug 8] Why this alone is Option D — the mechanism, verified

Setting that one key does **two** things, not one. The second was missed on July 30 because of §4's error.

**1. It fixes the CSV button.** `querySelector('.views-table')` finds the table in any theme.

**2. It fixes the table styling, with no new CSS**, by activating `user-pages.css:407` onward. The chain,
each link checked against the working tree:

| Link | Evidence |
|---|---|
| The Table style *has* a `class` option | `core/modules/views/src/Plugin/views/style/Table.php:74` — `$options['class'] = ['default' => '']` |
| It reaches the `<table>` element | `core/modules/views/src/Hook/ViewsThemeHooks.php:916-918` copies it into `$variables['attributes']['class']` |
| Theme-independently | Core's `views-view-table.html.twig:37-42` then does `attributes.addClass(...)`, appending `cols-6 responsive-enabled`. Result in **both** themes: `<table class="views-table cols-6 responsive-enabled">` |
| The stylesheet is on the page | `user-pages.css` is in the theme's **`global`** library, attached via `ccsoccer_theme.info.yml` — every page the theme renders, `/admin/*` included |
| The selector's scope matches | `page.html.twig:92` opens `.site-main__inner`; `{{ page.content }}` is inside it at `:108` |
| Nothing resolves to empty | All 13 custom properties used by the block are defined in `tokens.css` |
| `ccsoccer_theme` does not interfere | It overrides **no** views templates (`templates/` holds only `html`, `page`, `page--system-403/404`, `menu-local-task[s]`, `status-messages`) |

**Two mechanism details worth knowing:**

- `:916-918` **assigns** rather than merges, so the class option would clobber any class an earlier
  preprocess had put on the table. Nothing does on this view — but it is not additive.
- With **no** `options` key at all (today's state) Views falls back to `defineOptions()` defaults, which is
  why the table renders correctly now. A *partial* `options` map merges over those defaults, so adding only
  `class` changes nothing else. Verified against `views.style.table` schema: `class` is declared and the
  mapping has no required keys.

**Why the CSV content is unaffected either way:** the script reads `textContent` from the same
`<th>`/`<td>` elements regardless of theme, so the downloaded file is byte-identical for Admin and Board
Member once the button fires. Theme only ever affected the *screen*. The view carries no `click_sort` on
any column — sorting is server-side on `season_name`, `last_name`, `first_name` — so no tablesort markup
contaminates the header row of the export.

**⚠ If D is chosen, the in-view comment is part of the change.** See §7.4. Recommended wording: name the
setting (`Format: Table > Settings > Table CSS classes`), state that both the download *and*
`user-pages.css:401-478` depend on it, and say that clearing it fails silently in both respects.

### 8.3 [Aug 8] Confidence — what is verified and what is not

**Verified by reading code** at `65d7915`: every row of the table in §8.2, the six field IDs
(`first_name`, `last_name`, `jersey_size`, `season_name`, `username`, `order_number` — matching the six
rendered columns), `pager: type: none`, the absence of `click_sort`, and the schema's acceptance of a
partial options map.

**Not verified by running it.** A LOCAL trial was prepared on Aug 8 and **reverted before import**, so
no environment has ever rendered this. The confirming test is a single page load as a Board Member —
§9's Option D block.

**The one thing most likely to be wrong** is visual rather than functional: the block was written for
*user profile* tables, so its card treatment (rounded corners, shadow) may read differently on an admin
report than it does on `/user/*`. Functionally it will work; whether it looks right is a judgement call
that wants eyes on it, and it is cheap to reverse.

## 9. Test plan (LOCAL, masquerading as a Board Member)

### 9.1 If Option A ships (the original plan)

- `/admin/ccsoccer/reports` renders in Claro; all four links present.
- **Jersey Report** → Download CSV produces a file. Change the Active Seasons filter, Apply, download again
  → the CSV reflects the filtered set, header row included.
- **Tournament Deposits** → table styled, **sort arrows visible**, sorting works, filter select inline,
  Refund / Forfeit links reachable.
- **City Payment** and **Insurance** → PDFs download exactly as before. These should be byte-identical;
  they never touched the theme.
- **Front-end pages** (Home, Teams, Schedule, Register, My Registrations) → **still `ccsoccer_theme`,
  unchanged.** The permission only affects `/admin/*`. This is the check that proves the blast radius.
- **Notification node add/edit** → now Claro. Confirm the form still saves correctly (§7.2).
- **Control:** revoke the permission and re-check the jersey button. If §8 has landed it should *still*
  work; if only the permission was granted it will break again — which is precisely §7.3.

### 9.2 [Aug 8] If Option D ships

Everything here is as a **Board Member**, still in `ccsoccer_theme` — the page must *not* switch to Claro.

- **Jersey Report** → the table now has a grey header band, padded cells and row dividers. Compare against
  the Admin/Claro rendering: it should read as equivalent or better, not merely different (§8.3).
- **Download CSV produces a file.** Then change the Active Seasons filter, Apply, download again → the CSV
  reflects the filtered set, header row included.
- **Diff the Board Member CSV against an Admin CSV of the same filter — they must be byte-identical.**
  This is the check that proves the theme never touched the data (§8.2).
- **Column count and order unchanged** — six columns, First Name → Order Number. This is the regression to
  watch if the full options map is written by hand rather than left partial (§10.2).
- **Front-end pages** (Home, Teams, Schedule, Register, My Registrations) → unchanged. `.views-table` is
  now emitted on this one view; confirm no other page picked up unintended styling from
  `user-pages.css:401-478`.
- **As Admin/Claro** → the Jersey Report still renders correctly. The class simply appears twice, which is
  harmless — but confirm rather than assume.
- **Control for §7.4:** clear the Table CSS classes field and reload. Both the download *and* the styling
  should break together. That is the dependency the in-view comment exists to advertise.

## 10. Deploy

Config change, not code — under **either** option.

### 10.1 [Aug 8] Do not use `cex`, and do not use a blanket `cim`

The July 30 text below recommended granting the permission in the UI and then `drush cex`. **Two problems
were found on Aug 8:**

- **`cex` is the dangerous direction.** It sweeps LOCAL active config into `config/sync`, which is exactly
  the "dev changes overwrite prod configuration" shape this project has been bitten by. Neither option
  needs it: if the YAML is hand-edited, `config/sync` is already correct and there is nothing to export.
- **The UI path contradicts the repo's own stated convention** — `SESSION_HANDOFF.md` says *"Edit config
  YAML directly in `config/sync` rather than via UI to avoid drift."*

**Use a targeted partial import instead.** Drush 13.7.3 supports it (`ConfigImportCommands.php:157-159`):

**On LOCAL (ddev)** — ⚠ the temp directory **must be inside the project**. `ddev drush` runs inside the web
container, where `~` is the container's home, not your Mac's, and only the project directory is mounted
(at `/var/www/html`):

```bash
mkdir -p .cfg-partial
cp config/sync/<the one or two files> .cfg-partial/

ddev drush config:status                                                       # expect three media_library entries
ddev drush config:import --partial --source=/var/www/html/.cfg-partial --diff  # preview
ddev drush config:import --partial --source=/var/www/html/.cfg-partial -y
ddev drush cr
rm -rf .cfg-partial
```

**On TEST / PROD** (native drush, real home directory) the same thing with `"$HOME/ccs-cfg-partial"` as
the source, behind the usual `PATH=/opt/cpanel/ea-php83/... drush.php -r web` form.

⚠ **Do not write `--source=~/dir`.** Bash does **not** expand a tilde after `=` in a command argument, so
drush receives a literal `~`. Use `"$HOME/…"` or an absolute path. (`--source ~/dir` with a space does
expand — quoting `$HOME` is unambiguous.)

`--partial` processes only updates and new configs and deletes nothing; the blast radius is exactly the
files copied into that directory. **The command is not identical across environments** — see above.

> ### ⚠ `--partial` bypasses `config_ignore`
>
> Drush's own description: *"No config transformation happens."* Config transformation is the event
> `config_ignore` hooks into — so the four ignored patterns (`commerce_payment.*`, `update.settings`,
> `recaptcha.settings`, `captcha.captcha_point.*`) are **not** protected on a partial import. The temp
> directory is the only safety net. Never copy an ignored file into it.
>
> **This applies project-wide, not just to this decision.** It belongs in `SESSION_HANDOFF.md`'s gotchas.

**Context for whichever option ships:** as of `65d7915` the entire `config/sync` delta since `8e7b0be` is
**one file** (`field.field.user.user.field_skill_level.yml`, from PR #131). So the deploy for this decision
is one or two files either way, and a blanket `cim` is never warranted. This also resolves the standing
contradiction between `OUTSTANDING_ISSUES.md` §0.7 ("code-only: no `updb`, no `cim`") and
`SESSION_HANDOFF.md`'s Aug 7 note ("`drush cim` then `drush cr`. Both are required").

### 10.2 [Aug 8] If Option D ships: partial options map, or full?

`views.view.jersey_report_view.yml` currently has **no** `options` key under `style:` at all. Two ways to
add the class:

- **Minimal** — add only `options: {class: views-table}`. Three-line diff, schema-valid, and Views merges
  the remaining defaults at runtime so behaviour is otherwise unchanged. Follows the stated
  hand-edit-the-YAML convention.
- **Full map** — write out `grouping`, `row_class`, `default_row_class`, `columns`, `default`, `info`,
  `override`, `sticky`, `order`, `caption`, `summary`, `description`, `empty_table` as well. Matches the
  *file shape* of every other view in `config/sync`, but that shape is an artifact of those views having
  been saved through the Views UI — it is not a stated rule, and ~90 lines of hand-written YAML is ~90
  lines in which a mistyped field ID silently drops or merges a column.

**The tradeoff:** minimal is safer now but will produce a large diff the first time anyone saves that view
in the UI (Views writes the full map, `config:status` then reports `Different`). Full map avoids that but
carries transcription risk today. **Andrew's and Caleb's call** — if the full map is chosen, §9.2's column
count/order check stops being a formality.

### 10.3 The original July 30 deploy note

Preferred path, given the known `media_library` drift in `drush config:status`: grant the permission in the
UI at `/admin/people/permissions`, then export just that role — avoids importing unrelated drift.

```
drush config:status                    # expect only the three known media_library entries
drush cex                              # review the diff: user.role.board_member.yml only
```

Then TEST → soak → PROD per normal. `drush cr` after import.

**Superseded by §10.1** — kept because the `media_library` drift caveat and the review-the-diff discipline
still apply.

## 11. Questions for Caleb

1. **Was the front-end theme ever an intended rendering target for admin pages?** `admin-mobile.js/css`
   suggests yes at least once; `user-pages.css:401-478` and `insurance-report.css` suggest no. Knowing the
   intent decides whether §4 is cleanup or a deliberate deferral.
2. **Any objection to Board Members seeing Claro on notification node forms** (§7.2)? That is the one
   surface where the change is user-visible beyond the reports.
3. **Should `view the administration theme` travel with report access as a rule?** i.e. if Tournament
   Director is ever separated from Board Member, does it need the same grant? (Currently moot — the
   Tournament Director *is* a Board Member — but it will matter if the roles split.)
4. Is the dead CSS in §4 worth a cleanup ticket, or leave it?

**[Aug 8] Added, given Option D:**

5. **A or D, or both?** The honest framing is in the §6 addendum. The crux: **A fixes all four reports and
   D fixes one** — but D needs no permission change, and the one it fixes is the one that was reported.
   If D alone, **Tournament Deposits keeps its invisible sort arrows** (§3.1), which this document rates a
   real usability loss rather than cosmetics. Is that acceptable, or does it just defer the argument?
6. **Does question 1 above have a different answer now?** It asked whether the front-end theme was ever an
   intended rendering target for admin pages. Under D the answer becomes *yes, for this report at least* —
   which makes `user-pages.css:401-478` supported code rather than an accident, and makes the next
   admin-facing stylesheet's target a live question rather than a rhetorical one.
7. **Is reviving a stylesheet written for `/user/*` pages the right call for an admin report**, or would
   you rather see purpose-built rules? See §8.3 — it will function; whether it *looks* right is the open
   question, and it is cheap to reverse either way.
8. **Where should the `--partial` / `config_ignore` interaction be recorded?** §10.1 is project-wide
   knowledge that outlives this decision and arguably belongs in `SESSION_HANDOFF.md`'s gotchas.

## 12. Also noticed, unrelated to the decision

`ReportController::reportsLanding()` gates the **Jersey Report** link on `view reports` only, while the
other three accept `view reports` **or** their specific permission
(`access city payment report` / `access tournament deposits report` / `access insurance report`). A role
holding `access jersey report` without `view reports` would see no link but could still reach the URL
directly. Invisible today because Board Member holds both. Worth aligning when someone is next in that file.

---

## Appendix — file references

| Claim | Source |
|---|---|
| `/admin/*` routes are auto-marked admin routes | `web/core/lib/Drupal/Core/EventSubscriber/AdminRouteSubscriber.php:20-22` |
| Only `content_editor` has `view the administration theme` | sweep of `config/sync/user.role.*.yml` |
| Board Member permission list | `config/sync/user.role.board_member.yml` |
| Front-end theme has no base theme | `web/themes/custom/ccsoccer_theme/ccsoccer_theme.info.yml:6` |
| `views-table` is Claro-only | `core/themes/claro/templates/views/views-view-table.html.twig:34-42` vs `core/modules/views/templates/views-view-table.html.twig:37-41` |
| Jersey view access + inline CSV script + table style | `config/sync/views.view.jersey_report_view.yml:361-364`, `:477`, `:453-454` |
| Table style `class` option exists and is consumed | `core/modules/views/src/Plugin/views/style/Table.php:74`; `core/modules/views/src/Hook/ViewsThemeHooks.php` ~915-919 |
| City Payment / Insurance produce server-side PDFs | `Service/PdfService.php:162-170`; `Service/InsuranceReportService.php:63-68` |
| Tournament Deposits render array | `Controller/ReportController.php:373`, `:387`, `:393`, `:397-403` |
| Deposits sorting is server-side | `Controller/ReportController.php:209`, `:260` |
| Dead Claro-scoped CSS | `ccsoccer_theme/css/user-pages.css:401-478`; `ccsoccer/css/insurance-report.css` — ⚠ **see the §4 correction: the first is unreached, not dead** |
| **[Aug 8]** Table style has a `class` option | `core/modules/views/src/Plugin/views/style/Table.php:74` |
| **[Aug 8]** The option reaches the `<table>` in any theme | `core/modules/views/src/Hook/ViewsThemeHooks.php:916-918`; `core/modules/views/templates/views-view-table.html.twig:37-42` |
| **[Aug 8]** `user-pages.css` loads on every themed page | `ccsoccer_theme.libraries.yml:11` (`global` library) + `ccsoccer_theme.info.yml` `libraries:` |
| **[Aug 8]** `.site-main__inner` wraps page content | `ccsoccer_theme/templates/page.html.twig:92`, `{{ page.content }}` at `:108` |
| **[Aug 8]** `ccsoccer_theme` overrides no views templates | `ls ccsoccer_theme/templates/` |
| **[Aug 8]** Jersey view: no pager, no click-sort, six fields | `config/sync/views.view.jersey_report_view.yml` — `pager: type: none`; sorts on `season_name`/`last_name`/`first_name` |
| **[Aug 8]** `--partial` exists and skips config transformation | `vendor/drush/drush/src/Commands/config/ConfigImportCommands.php:157-159` |
| `insurance-report` library attachment | `ccsoccer.module:2792-2796` |
| Theme-agnostic precedent | `ccsoccer.libraries.yml:79-88`; `js/admin-mobile.js:6-10, 24-28`; `css/admin-mobile.css:11-15` |
| Reports landing permission gating | `Controller/ReportController.php:129-160` |
