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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-04-25
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## Context

The `RegisteredCommandAdmin.name_link` method renders the command description as an inline-styled `<span>` with a hard-coded `max-width:300px`. This fixed width truncates descriptions regardless of the actual column width available in the admin changelist table. On wider screens, the column has more space than 300px, yet descriptions still cut off prematurely.

Additionally, the `list.html` templates (both base and Unfold variants) render descriptions without any truncation, which can cause layout overflow with very long help text.

## Goals / Non-Goals

**Goals:**
- Make description truncation responsive to the actual available container width, not a fixed pixel value.
- Apply consistent truncation behavior across the admin changelist and both list template variants.
- Use pure CSS (no JavaScript) for the truncation.

**Non-Goals:**
- Adding a tooltip/popover on hover to show the full description (could be a future enhancement).
- Changing the visual appearance beyond the truncation behavior.
- Adding any JavaScript or dynamic resizing logic.

## Decisions

### Use CSS `max-width:100%` with `overflow:hidden; text-overflow:ellipsis` on a block-level element

**Decision**: Replace `max-width:300px` with `max-width:100%` (or remove max-width entirely and rely on the parent's natural constraints), keep `overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:block`, and ensure the parent `<td>` constrains width properly.

**Rationale**: The admin changelist table cell already has a natural width. Using `max-width:100%` lets the description span the full available column width. `text-overflow:ellipsis` only activates when content actually overflows — short descriptions render in full, long ones get the ellipsis. This is pure CSS, zero JavaScript, and works across all browsers.

**Alternative considered**: Using JavaScript to detect overflow and add a title attribute. Rejected because it adds complexity and a JS dependency for a purely presentational concern.

**Alternative considered**: Using `-webkit-line-clamp` for multi-line truncation. Rejected because the current design is single-line and line-clamp has inconsistent browser support for the ellipsis indicator in all contexts.

### Apply the same truncation CSS in list.html templates

**Decision**: Add `overflow:hidden; text-overflow:ellipsis; white-space:nowrap;` to the description elements in both `unfold/list.html` and `base/list.html`.

**Rationale**: Very long management command help text can break layouts. Applying consistent truncation prevents overflow while still showing as much as fits.

## Risks / Trade-offs

- **[Risk] Column width may vary by browser/screen size** → Acceptable trade-off; the behavior is "truncate only when needed" which is the desired outcome. The column will naturally size based on Django admin's table layout.
- **[Risk] Very narrow viewports may still truncate aggressively** → This is inherent to CSS truncation and matches user expectations. The alternative (wrapping) would make the changelist harder to scan.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Why

Command descriptions in the admin changelist are truncated at a hard-coded `max-width:300px` with CSS `text-overflow:ellipsis`. This cuts off short descriptions that happen to wrap, while long descriptions may still feel too wide. The truncation should be based on the actual available space in the column rather than an arbitrary fixed width.

## What Changes

- Remove the hard-coded `max-width:300px` from the description `<span>` in `RegisteredCommandAdmin.name_link`.
- Apply CSS-based truncation that uses the full available column width (e.g., `max-width:100%` or width relative to the parent) so descriptions only truncate when they genuinely overflow their container.
- Apply the same responsive truncation to the description text in the Unfold `list.html` template (currently untruncated, which can cause layout issues with very long help text).
- Apply the same to the base `list.html` template description column.

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `command-admin-changelist`: Description truncation in the admin changelist will use container-relative sizing instead of a fixed pixel width.

## Impact

- `src/django_admin_runner/admin.py` — `name_link` method CSS changes
- `src/django_admin_runner/templates/django_admin_runner/unfold/list.html` — add truncation CSS to description
- `src/django_admin_runner/templates/django_admin_runner/base/list.html` — add truncation CSS to description
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## MODIFIED Requirements

### Requirement: Changelist shows command metadata columns
The `RegisteredCommand` changelist SHALL display the following columns: `name`, `display_name`, `group`, `description` (truncated), `active`, `updated_at`, and a custom "Run" action column. The description SHALL use CSS `overflow:hidden; text-overflow:ellipsis; white-space:nowrap` on a block-level element without a fixed `max-width` in pixels, so that truncation only occurs when the text overflows the natural column width.

#### Scenario: Active command displayed in changelist
- **WHEN** the changelist is viewed and a `RegisteredCommand` with `name="cleanup_books"`, `group="Books"`, `active=True` exists
- **THEN** a row is shown with those values and a "Run" link pointing to the command run view

#### Scenario: Short description fits without truncation
- **WHEN** a `RegisteredCommand` has a description that fits within the column width
- **THEN** the full description is displayed without an ellipsis

#### Scenario: Long description is truncated with ellipsis
- **WHEN** a `RegisteredCommand` has a description longer than the available column width
- **THEN** the description is truncated with an ellipsis (`…`) at the point where it overflows

#### Scenario: No hard-coded pixel width on description
- **WHEN** the description `<span>` element is rendered
- **THEN** it SHALL NOT have a `max-width` set to a fixed pixel value (e.g., `300px`)

## ADDED Requirements

### Requirement: List templates truncate long descriptions
Both the base and Unfold `list.html` templates SHALL apply CSS truncation (`overflow:hidden; text-overflow:ellipsis; white-space:nowrap`) to command description text, so that very long help text does not break the layout.

#### Scenario: Long description in Unfold list template
- **WHEN** a command's help text exceeds the available width in the Unfold list view
- **THEN** the description is truncated with an ellipsis

#### Scenario: Long description in base list template
- **WHEN** a command's help text exceeds the available width in the base list view
- **THEN** the description is truncated with an ellipsis
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## 1. Admin changelist description truncation

- [x] 1.1 In `admin.py` `RegisteredCommandAdmin.name_link`, replace the hard-coded `max-width:300px` with `max-width:100%` on the description `<span>`, and change `display:inline-block` to `display:block` so the element fills its parent column width
- [x] 1.2 Verify the description renders correctly in the Django admin changelist — short descriptions show in full, long ones truncate with ellipsis

## 2. List template description truncation

- [x] 2.1 In `templates/django_admin_runner/unfold/list.html`, add `style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"` to the description `<p>` element
- [x] 2.2 In `templates/django_admin_runner/base/list.html`, add `style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"` to the description `<td>` element

## 3. Verification

- [x] 3.1 Run `just check` to ensure linting passes
- [x] 3.2 Run `just tests` to ensure all tests pass
25 changes: 24 additions & 1 deletion openspec/specs/command-admin-changelist/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,24 @@ The system SHALL register a `ModelAdmin` for `RegisteredCommand` that disables a
- **THEN** no "Add", "Edit", or "Delete" buttons or actions are shown

### Requirement: Changelist shows command metadata columns
The `RegisteredCommand` changelist SHALL display the following columns: `name`, `display_name`, `group`, `description` (truncated), `active`, `updated_at`, and a custom "Run" action column.
The `RegisteredCommand` changelist SHALL display the following columns: `name`, `display_name`, `group`, `description` (truncated), `active`, `updated_at`, and a custom "Run" action column. The description SHALL use CSS `overflow:hidden; text-overflow:ellipsis; white-space:nowrap` on a block-level element without a fixed `max-width` in pixels, so that truncation only occurs when the text overflows the natural column width.

#### Scenario: Active command displayed in changelist
- **WHEN** the changelist is viewed and a `RegisteredCommand` with `name="cleanup_books"`, `group="Books"`, `active=True` exists
- **THEN** a row is shown with those values and a "Run" link pointing to the command run view

#### Scenario: Short description fits without truncation
- **WHEN** a `RegisteredCommand` has a description that fits within the column width
- **THEN** the full description is displayed without an ellipsis

#### Scenario: Long description is truncated with ellipsis
- **WHEN** a `RegisteredCommand` has a description longer than the available column width
- **THEN** the description is truncated with an ellipsis (`…`) at the point where it overflows

#### Scenario: No hard-coded pixel width on description
- **WHEN** the description `<span>` element is rendered
- **THEN** it SHALL NOT have a `max-width` set to a fixed pixel value (e.g., `300px`)

### Requirement: Run action links to the command run view
Each row in the changelist SHALL display a "Run" link (only for active commands) that navigates to the existing `django_admin_runner_command_run` URL for that command.

Expand Down Expand Up @@ -58,3 +70,14 @@ The file `src/django_admin_runner/templates/admin/index.html` SHALL be deleted.
#### Scenario: Template file no longer exists
- **WHEN** the package is installed
- **THEN** no `admin/index.html` template override exists in the package's template directory

### Requirement: List templates truncate long descriptions
Both the base and Unfold `list.html` templates SHALL apply CSS truncation (`overflow:hidden; text-overflow:ellipsis; white-space:nowrap`) to command description text, so that very long help text does not break the layout.

#### Scenario: Long description in Unfold list template
- **WHEN** a command's help text exceeds the available width in the Unfold list view
- **THEN** the description is truncated with an ellipsis

#### Scenario: Long description in base list template
- **WHEN** a command's help text exceeds the available width in the base list view
- **THEN** the description is truncated with an ellipsis
2 changes: 1 addition & 1 deletion src/django_admin_runner/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def name_link(self, obj: RegisteredCommand) -> SafeString:
if obj.description:
desc_html = (
f'<br><span style="color:var(--body-quiet-color,#666);'
f"max-width:300px;display:inline-block;overflow:hidden;"
f"max-width:100%;display:block;overflow:hidden;"
f"text-overflow:ellipsis;white-space:nowrap;"
f'font-size:12px;">{obj.description}</span>'
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ <h2 style="padding: 0.5em 1em; margin: 0; background: var(--darkened-bg, #f8f8f8
{% for cmd in commands %}
<tr class="{% cycle 'row1' 'row2' %}">
<td><a href="{% url 'admin:django_admin_runner_command_run' cmd.name %}"><strong>{{ cmd.name }}</strong></a></td>
<td style="color: var(--body-quiet-color, #666);">{{ cmd.command_class.help|default:"" }}</td>
<td style="color: var(--body-quiet-color, #666); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">{{ cmd.command_class.help|default:"" }}</td>
<td style="text-align: right; white-space: nowrap; display: flex; gap: 6px; justify-content: flex-end;">
<a href="{% url 'admin:django_admin_runner_command_run' cmd.name %}" class="button">
{% trans "Run" %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<a href="{% url 'admin:django_admin_runner_command_run' cmd.name %}" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors">{{ cmd.name }}</a>
</p>
{% if cmd.command_class.help %}
<p class="text-xs text-font-default-light dark:text-font-default-dark mt-0.5">
<p class="text-xs text-font-default-light dark:text-font-default-dark mt-0.5" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
{{ cmd.command_class.help }}
</p>
{% endif %}
Expand Down
Loading