Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ The catalogue exists in **two forms**, and they must not drift:

## Non-obvious invariants

### Bootstrap lives in `@layer bootstrap` — and `!important` reverses that

- For **normal** declarations, unlayered CSS (ours, and an integrator's) beats layered
Bootstrap. That is the whole point of `css/foundations/_bootstrap-layer.css`.
- For **`!important`** declarations the layer order is reversed (CSS Cascading 5 §6.4): a
layered important declaration beats an unlayered one. **Every Bootstrap utility is
`!important`**, so a `.w-100` in the markup silently defeats an unlayered
`.my-column { width: 350px !important }`. There is nothing to escalate to — the fix is to
drop the utility from the markup.
- This is the real reason `components/` may not use `!important`: ours would lose too.
- Integrators upgrading to v3 must audit every `!important` of theirs sitting on an element
that also carries a Bootstrap utility for the same property. The relation flipped.

### `User` entity (MappedSuperclass)

- `User` is `#[ORM\MappedSuperclass]` — instantiate directly with `new User()`, never via a factory.
Expand Down
29 changes: 28 additions & 1 deletion doc/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ To use this macro, you first need to import it in your Twig template:

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `item` | `object` | The entity instance (used to generate the CSRF token for deletion). |
| `item` | `object` | The entity instance. Only used as the fallback CSRF token id (`delete<id>`), which matches no shipped controller — pass `delete_token` instead. |
| `edit_path` | `string` | (Optional) The URL for the "Edit" action. If omitted, the edit link will not be displayed. |
| `delete_path` | `string` | (Optional) The URL for the "Delete" action. If omitted, the delete link will not be displayed. |
| `delete_confirm_msg` | `string` | (Optional) A custom confirmation message for the deletion. Defaults to the translation of `text.confirm_delete`. |
| `status` | `hash` | (Optional) The online/offline toggle: `{ path, confirm, label }`. `confirm` may contain `%s`, replaced at runtime by the target state. |
| `delete_token` | `string` | (Optional) The CSRF token **value** for the delete form, e.g. `csrf_token('delete__post' ~ post.id)`. Each controller validates its own id, so the caller must compute it. |
| `extra` | `Markup` | (Optional) Caller-supplied markup inserted between the edit link and the status toggle — a preview link, a modal trigger, anything the fixed slots cannot express. Capture it with `{% set %}` so it is not escaped. |

### Example

Expand All @@ -49,6 +52,30 @@ In a DataTable row:
{% endblock %}
```

### Adding an item the macro does not cover

Capture the markup, then pass it by name — `extra` sits after `delete_token` in the
signature, so a named argument keeps the call readable:

```twig
{% set extra %}
<a href="{{ item.previewLink }}" target="_blank" class="dropdown-item">
{{ ux_icon('lucide:link') }} {{ 'text.preview'|trans }}
</a>
{% endset %}

{{ list.actions(
item,
path('admin_event_edit', {id: item.id}),
path('admin_event_delete', {id: item.id}),
extra: extra,
delete_token: csrf_token('delete__event' ~ item.id),
) }}
```

`{% set %}` yields a `Markup` object, which Twig prints as-is. A plain string passed here
would be escaped and show up as literal HTML.

## Breadcrumb Macro

The `breadcrumb` macro generates a navigation breadcrumb list from an array of items.
Expand Down
7 changes: 7 additions & 0 deletions doc/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ Override only these — the semantic layer. Values below are the defaults.
> The `-contrast` companion of each (`--aro-color-primary-contrast`…) is the text colour
> drawn on top of it — set it too if your brand colour needs dark text.

> **Override the `-rgb` companion with its colour.** Every bridged colour has an
> `--aro-color-*-rgb` twin holding its bare `r, g, b` triplet (`--aro-color-secondary-rgb:
> 46, 79, 94`). Bootstrap's utilities (`.bg-*`, `.text-bg-*`, focus rings) consume the
> triplet, not the hex — re-theme a colour without its twin and those utilities keep the
> default palette. No override rule can catch them either: the layered `!important` on
> Bootstrap's utilities beats any unlayered one. The variable pair is the seam.

### Semantic

`--aro-color-success` `#63CEB3` · `--aro-color-danger` `#E52321` ·
Expand Down
35 changes: 35 additions & 0 deletions doc/upgrade-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,41 @@ A `data-toggle="modal"` left over from Bootstrap 4 raises no error — the butto
nothing. Grep your admin templates for `data-toggle`, `data-target`, `data-dismiss` and add the
`-bs-` (see above).

### Select2 AJAX payload

The `processResults` shipped in `app.js` changed shape. Before:

```js
results: data.items,
pagination: { more: (params.page * 20) < data.total_count }
```

Now:

```js
results: data.results,
pagination: { more: data.pagination.more }
```

An endpoint still answering `{ total_count, items }` **fails silently in the worst way**: the
request fires, the response arrives, and the list renders empty — nothing in the console points
at the payload. Every AJAX Select2 endpoint must now answer:

```json
{
"results": [ { "id": 12, "full_name": "…" } ],
"pagination": { "more": true },
"total_count": 137
}
```

`full_name` may be an HTML fragment: `escapeMarkup` is the identity function and
`templateResult` reads `repo.full_name || repo.text`, so a row carrying neither renders blank.

`Component\Select2\Select2` emits this shape for you — but only for Doctrine-backed lists (it
ends in a `QueryBuilder`, `Select2DataProviderInterface` included). A list fed by a search
engine or a remote API has to build the payload itself; the contract above is all it owes.

---

## Rich-text editor — CKEditor → Quill
Expand Down
Loading