24487 Commits

Author SHA1 Message Date
Miguel de Benito Delgado
dc22e2d77f 🚧 WIP Step 21 Refactor namespaces 2026-10-28 19:17:39 +01:00
Miguel de Benito Delgado
23746893e6 🚧 WIP Step 20 test last round of features 2026-10-28 19:17:39 +01:00
Miguel de Benito Delgado
27e4597b45 🚧 WIP Step 19.2 Missing browser utils 2026-10-28 19:17:39 +01:00
Miguel de Benito Delgado
8140e751b8 🚧 WIP Step 19.1 Allow selecting cases in pilot 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
494abd17dd 🚧 WIP Step 19 New scenes, use camera 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
0eeba84af6 🚧 WIP Step 18 Camera ops 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
9cb53adb44 🚧 WIP Step 17 Support path construction 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
5ca9e06fa7 🚧 WIP Step 16.4 Refactor failures, remove legacy validation, cleanup 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
a76f225ba2 🚧 WIP Step 16.3 Cleanup pilot 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
54a4b70001 🚧 WIP Step 16.2 Renames and fixes 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
aa7bd111f8 🚧 WIP Step 16.1 Relax scene macro constraints 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
6cec9d7be4 🚧 WIP Step 16 Introduce hooks for the browser and load-scene 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
b01cdbe880 🚧 WIP Step 15.1, cosmetic 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
b982e864bd 🚧 WIP Step 15, Use transit to simplify validation 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
634b74d0c6 🚧 WIP Step 14, Grab correct w/h attrs from context 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
680408796a 🚧 WIP Step 13, Track failure causes
AI-assisted-by: muse-spark, GLM-5.3
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
9356c3a225 🚧 WIP Step 12.4, Comments
AI-assisted-by: gpt6-astra
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
5224a54cb7 🚧 WIP Step 12.3, Test cleanup + missing coverage
AI-assisted-by: muse-spark, deepseek-flash
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
9f945de1a6 🚧 WIP Step 12.2, Fix browser bridge, lifecycle
AI-assisted-by: muse-spark, glm-5.3
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
3b8b417045 🚧 WIP Step 12.1, Improve case validation
AI-assisted-by: muse-spark, glm-5.3
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
9e5620600f 🚧 WIP Step 12, temporary pilot
- pilot runner for testing
- will be entirely removed later

AI-assisted-by: muse-spark-1.3, deepseek-flash, glm-5.3
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
5afac4a330 🚧 WIP Step 11, browser bridge
- New schema:viewport
- Browser bridge (with some temp code)
- Move prepare-scene out measured regions
-
AI-assisted-by: muse-spark-1.3, deepseek-flash, glm-5.3
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
4e9d83b4ad 🚧 WIP Step 10, scene upload 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
d46f5e59c6 🚧 WIP Step 10, defcase body validation, docs 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
8d04530e07 🚧 WIP Step 9, fix naming conventions 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
c16233f841 🚧 WIP Step 8, simplify and clean up tests 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
44dfb47c98 🚧 WIP Step 7, scene and case declarations 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
551c3bffb7 🚧 WIP Step 6, ellipses 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
bfde40d1ab 🚧 WIP Step 5, bools 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
bd17e6d431 🚧 WIP Step 4, frames and groups 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
df4ec3ea33 ✨ Add --no-build to legacy bench runner 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
9d281b225e 🚧 WIP Step 3, fixture macros, prng, defaults, placeholder scene 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
01f6eb2a7a 🚧 WIP Step 2, common scene code, validation 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
50c72a1ad9 🚧 WIP Step 1, scaffold cljs benchmark runner 2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
50e2bf02c8 🐛 Fix regex filtering cargo feature names
Now allows underscores too
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
3b35187676 ✨ Expose the playground adapter for console access
mountScenario now assigns the mounted renderer to
globalThis.playgroundAdapter and removes it on dispose, so the
wasm module is reachable from the browser console on every
playground page (for example playgroundAdapter.module._render_stats()).

The benchmark verify-pages diagnostic already used this name by
patching the served lib.js. The source is now the canonical
writer, so the wrapper's assignment is redundant.

AI-assisted-by: deepseek-flash
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
23109b71fe 🐛 Fix playground rendering at non-unit device pixel ratio
The playground called _init and _set_render_options with the DPR,
which sets the ratio but leaves the Skia target, backbuffer and
UI surfaces at CSS resolution. The canvas drawing buffer is
physical (CSS x DPR), so at DPR > 1 the smaller surface anchored
to the bottom-left of the framebuffer and left the top and right
edges unpainted.

Call _resize_viewbox after setting the DPR, the same sequence the
application uses in resize-canvas!, so the renderer surfaces
match the physical drawing buffer.

Cover DPR 1, 1.25 and 2 in the adapter test.

AI-assisted-by: deepseek-flash
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
34e4dc1674 ✨ Coalesce playground wheel zoom per animation frame
Apply at most one _set_view + _render_from_cache per animation
frame in the wasm playground, accumulating the wheel zoom factor
and the last cursor point instead of rendering synchronously on
every wheel event.

This mirrors schedule-zoom! in
app.main.ui.workspace.viewport.actions so playground
interactions match the application. A comment records that the
playground should call the real function once it migrates to
ClojureScript.

Extend the adapter tests with the coalesced flush, factor reset
across frames, scale clamps, and per-move pointer pan.

AI-assisted-by: deepseek-flash
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
f81696886a ✨ Add feature-flag A/B harness to renderer benchmarks
Compare every on/off combination of requested Cargo features over the
Boolean-lattice edges, in Gray-code build order, with a matrix index of
configs, edge comparisons and provenance.

Single runs record explicit feature sets as schemaVersion 2 with resolved
defaults and build environments. Edge comparisons stay diagnostic; edges
clean apart from the intended toggle are marked controlled.

Validate with 68 focused tests, CLI fault checks, dry runs, and container
smoke matrices for branch-b pan cases.

AI-assisted-by: muse-spark-1.3-contributor-free
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
d0a8873e66 ✨ Lock shadow fixture and record sampling tradeoff
Add a regression test that keeps 1000 shadowed shapes with two
drop shadows and one inner shadow, seven FFI arguments, and
visible hidden flags. Record isolated load success against pan
and zoom context loss on SwiftShader without changing workloads.
Keep 3 warm-ups, 10 attempts, and 30s timeout provisional with
measured per-attempt costs and workflow guidance.

AI-assisted-by: muse-spark-1.3
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
0ae78cf7ec ✨ Verify benchmark failures and improve reports
Record manifest-default features without adding A/B selection. Keep
scored builds free of the optional function-name profiling switch.

Exercise CLI persistence, interruption, browser failure and legacy page
interactions through separate diagnostics. Share readable reports and
warn when offline comparisons include failed runs.

Validate with 50 focused tests, browser and CLI fault checks, two full
corpus workflow runs, and headed rectangle cases. Shadow graphics and
sampling calibration remain open.

AI-assisted-by: GPT-6
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
731bcc5664 🎉 Add playground renderer benchmark suite
Share seven deterministic fixtures with the playground pages and measure
renderer lifecycle, pan, and zoom through a prepared Playwright runner.
Retain raw attempts and failures with offline statistical comparisons.

Add focused tests and separate browser fault diagnostics. Full-suite
calibration and shadow GPU validation remain tracked follow-up work.

AI-assisted-by: GPT-6
2026-10-28 19:17:38 +01:00
Miguel de Benito Delgado
17489091c5 ♻️ Move use-shape into app.common.render_wasm
- Introduces a new ns since there wasn't a suitable one
- Simplifies serialize-shapes-batch! and allows usage outside the frontend
2026-10-28 19:17:38 +01:00
Dr. Dominik Jain
e20a0d748e
✨ Add OAuth support for MCP server (#12480)
* 🎉 Add OAuth support for remote MCP connections

Add MCP-specific discovery, client registration, and approval endpoints.
Exchange single-use authorization codes with PKCE for the fixed MCP user
token, enabling MCP and creating the token on approval when needed.

Use self-contained client IDs without new database tables or stored client
records. Keep temporary authorization state in Redis/Valkey.

Accept bearer credentials while preserving query-parameter connections.
Enable OAuth through PENPOT_MCP_AUTH_URI in multi-user mode. Document the
environment variable in the README and implementation details in Serena.

Explain protocol and concurrency decisions in the Clojure code. Cover token
provisioning, OAuth state, client registration, HTTP flows, and the approval
page with tests.

AI-assisted-by: gpt-6

* 🐛 Wait for devenv initialization before starting builds

Docker reports a container as Running as soon as its main process starts,
while the entrypoint may still be preparing the development environment.
manage.sh used that status to launch tmux jobs, allowing frontend builds
to race with ownership changes to /opt/rustup and Cargo setup.

On a fresh container, rustup could fail with Permission denied before the
frontend watcher started. Nginx would later serve the existing JavaScript,
leaving source changes absent from the running app despite restarting
devenv.

Wait for nginx's local health endpoint before launching the jobs. Nginx
starts after entrypoint setup, so its response confirms that setup has
finished. Allow up to 120 seconds and report a timeout without starting
the jobs if initialization does not complete.

Validation: shell syntax and whitespace checks pass.

AI-assisted-by: gpt-6

* ♻️ Share the login modal with MCP authorization

Extract the viewer login modal and its styles into a shared auth
component, retaining the viewer wrapper and its close behavior.

Use the modal on the MCP approval page and preserve the pending request
through login. Closing it clears the saved return URL and shows a
cancellation message without further actions.

Cover return URL handling and cancellation with frontend tests.

AI-assisted-by: gpt-6

* ✨ Show client icons in MCP authorization

Preserve HTTPS logo URLs from client registration and published metadata
and show the icon beside the application name on the approval page.
Ignore unusable URLs and hide failed images without blocking approval.

Update the access description to refer to MCP connections established
from Penpot. Cover logo metadata and approval responses in backend tests.

AI-assisted-by: gpt-6

* 🐛 Follow the system theme when no preference is configured

Default to the system theme in the shared resolver used by the app and
plugins. Preserve explicit light and dark choices, including the legacy
default value that means dark.

Reuse the reactive theme hook in progress widgets and show the system
preference in settings for accounts without a configured theme.

Verify preference resolution with regression tests and browser checks.
All 1,474 frontend tests pass.

AI-assisted-by: gpt-6

* 🐛 Replace expired MCP tokens during OAuth approval

Settings can give an MCP token an expiry date. OAuth approval reused
the newest MCP token without checking it, so an expired token reached
the client and never matched a plugin connection.

Approval now treats an expired token as missing and creates a new one.
Token exchange rejects a code whose token expired after approval with
invalid_grant.

AI-assisted-by: claude-opus-5-5

* 🐛 Log unexpected errors on MCP OAuth endpoints

The OAuth routes sit outside the shared HTTP middleware, so the shared
error handler never saw them, and their own handler turned unexpected
failures into a 503 without logging anything. Failures in the session
middleware of the approval endpoints had no handler at all.

Unexpected failures now also go through the shared error handler for
its logging, while clients still get an OAuth error body. The OAuth
error handler also wraps the session middleware of the approval
endpoints.

AI-assisted-by: claude-opus-5-5

* 🔧 Add devenv script to run the MCP server with OAuth

start-mcp-devenv-oauth starts the MCP server in multi-user mode with
PENPOT_MCP_AUTH_URI derived from PENPOT_PUBLIC_URI, to test OAuth in
the devenv. Agentic tooling still uses the single-user
start-mcp-devenv.

AI-assisted-by: claude-opus-5-5

* 🐛 Fix failing test case

---------

Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-10-28 18:40:04 +01:00
Eva Marco
8951c57ee6
♻️ Use DS buttons and icons in the dashboard fonts page (#12533)
* ♻️ Use DS buttons and icons in the dashboard fonts page

The fonts page moves off the deprecated icons and raw buttons:

- "Add custom font", "Upload all", "Upload" and "Save" become DS primary
  buttons and "Dismiss all" a secondary one.
- The remove, cancel and menu controls of the font rows become ghost DS
  icon buttons named "Remove", "Cancel" and "Options"; the menu one
  says whether its menu is open. Two of them were spans.
- The delete control of a font style was a span; it is now a button
  named "Delete font style" with the DS close icon, still shown only
  on hover.
- The warning and empty list icons use the DS icons. The loading
  placeholder keeps the legacy loader: there is no DS one.

The font menu offset grows by the 6px the taller menu button added, so
the menu opens where it did. New key: dashboard.fonts.delete-variant
(en, es). A visual test covers a font ready to upload, the style hover,
the font menu and the edition row.

AI-assisted-by: claude-opus-5-5

* ♻️ Move dashboard fonts styles to logical properties

Physical sizes, offsets, margins and paddings become logical properties
and the DS typography module is used without a namespace. The element
selectors (section title, inputs, hero text) stay, with a comment: they
have no class.

The shared dashboard header styles (common-dashboard) still come from
refactor/: ten dashboard modules use them, so they move in their own
change. No visual change.

AI-assisted-by: claude-opus-5-5

* ♻️ Use the DS loader and heading in the dashboard fonts page

The fonts loading placeholder used the legacy loader icon, drawn as an
outline in a 32px box. It now uses the DS loader*, the same animated
pencil as the other dashboard loading screens. Its box is 9px tall,
so the label moves 6px up.

The section title h3 is now a heading* with the title-small typography
and its own class, so the SCSS no longer styles the bare element.

The legacy loader icon has no other use, so its SVG and def are gone.

AI-assisted-by: claude-opus-5-5

* ♻️ Give the dashboard fonts inputs and hero text classes

The font name and search inputs and the hero text get their own
classes instead of bare input and p selectors, and the hero title uses
the DS heading with title-large instead of the global h2 rule. The
title keeps its 24px size and weight; the DS line height (1.4 instead
of 1.15) moves the hero content about 6px down.

AI-assisted-by: claude-opus-5-5

* 🐛 Give the fonts placeholder icon its color back

Removing the legacy loader also removed the .icon svg rule, which gave
the empty placeholder text icon its color, so the icon had none. The
icon now has its own class with the secondary foreground color, and the
wrapper div without styles goes.

AI-assisted-by: claude-opus-5-5
2026-10-28 16:11:52 +01:00
Eva Marco
66c67660f9
♻️ Use DS typographies in the debug shape panel and drop a dead selector (#12650)
* 🔥 Remove a commented-out selector from the debug shape panel

.shape-info-title was left commented out with a "check by hand" TODO in
#12484. Nothing uses it, so delete it instead of keeping it as a comment.

AI-assisted-by: claude-opus-5-5

* ♻️ Use DS typographies in the debug shape panel

The panel text and the shape title only set a font size, with a "no exact
DS typography match" TODO. body-small and body-medium differ from them
only in the line-height, so use them directly and drop the TODOs.

Visible change: the panel lines get the DS line-height (1.3 for the text,
1.4 for the shape title) instead of the browser default, so rows grow a
little taller. The panel is debug-only.

AI-assisted-by: claude-opus-5-5
2026-10-28 14:27:51 +01:00
Eva Marco
e308c2eca6
♻️ Use the DS stroke width in the migrated DS icons (#12673)
* ♻️ Use the DS stroke width in the migrated DS icons

The DS icons in the areas already moved to the DS set their own
stroke width (1px, 1.33px or 2.25px) to match the legacy icons. They
now keep the DS stroke width, so some icon lines get slightly thinner
or thicker: copy button, context menu, layer row, comments filter,
assets section, share link, dashboard sidebar, team pages, templates,
team switcher and subscription icons.

AI-assisted-by: claude-opus-5-5

* ♻️ Give the share link page rows a class

The page rows of the share link permissions get their own class
instead of a bare li selector. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-28 14:27:38 +01:00
Eva Marco
da36e36836
✨ Add the share, handlers and snap icons to the DS (#12659)
* ✨ Add the share, handlers and snap icons to the DS

The share button and the path editing bar could not move to the DS
buttons because their icons were missing from the DS icon set. As agreed
with design:

- handlers-equal, handlers-independent, handlers-mirror and snap join the
  DS as they are (their SVGs already follow the DS format).
- share keeps its drawing, moved onto the 16x16 grid as a single path
  with no fixed color.
- snap-nodes is deleted: nothing used it, and snap is the icon of the
  snap nodes button.

The legacy share button in the workspace header uses the same SVG: its
icon is about 6% smaller and now takes the hover color, which the fixed
stroke color blocked before.

AI-assisted-by: claude-opus-5-5

* 🐛 Show the share icon lines outside the app

The share SVG had no fill or stroke, so plain SVG viewers used the
defaults (black fill, no stroke) and drew only three dots. Set
fill="none" and stroke="currentColor", like settings.svg. The app
already sets both in CSS, so nothing changes there.

AI-assisted-by: claude-opus-5-5

* 🐛 Show the handlers and snap icons right outside the app

Like share, these SVGs had no fill or stroke, so plain SVG viewers
filled the shapes black and drew no lines. Set fill="none" and
stroke="currentColor". The app sets both in CSS, so nothing changes
there.

AI-assisted-by: claude-opus-5-5
2026-10-28 14:26:43 +01:00
Eva Marco
980ed0a719
♻️ Use DS icon buttons to close the subscription dialogs (#12539)
* ♻️ Use DS icon buttons to close the subscription dialogs

The close buttons of the five subscription dialogs (plan management,
success, Nitrate subscription, contact sales and manual cancel) were
raw buttons with no name. They become ghost DS icon buttons named
"Close"; the class that places them in the corner goes on the tooltip
wrapper, so they stay where they were and only show the DS hover.

The plans page link-style buttons stay raw: they have no DS equivalent.
A visual test covers the plans page and the plan management dialog.

AI-assisted-by: claude-opus-5-5

* ♻️ Move settings subscription styles closer to the DS

The settings subscription stylesheet inlines the deprecated
button-style mixin and %button-icon placeholder, resolves its legacy
color aliases and font weight, uses logical properties and the DS
typography module without a namespace. The rules on icon svgs that have
no class keep a comment; two that match nothing (the dropdown arrow svg
and .radio-btns) are commented out with a TODO.

refactor/ stays only for the modal placeholders (%modal-*), which move
with the modals refactor. No visual change.

AI-assisted-by: claude-opus-5-5

* 🔥 Remove unused styles from the settings subscription

The SCSS refactor of the settings subscription left rules commented out
with a TODO because the module does not use them, or because they target
an svg inside a DS icon that is the svg itself. CSS modules scope the
classes to this module and no other source uses them, so they go:
.icon-dropdown svg, .radio-btns, .radio-btns label. No visual change.

AI-assisted-by: claude-opus-5-5

* ♻️ Give the subscription icons classes and drop unused props

Review fixes for the subscription styles:

- The plan icon span sets the letter color with color, not stroke on a
  bare svg, and drops fill, stroke-width and color: transparent, which
  do nothing on a span.
- The plan icon border uses $b-2 (was 1.75px), so it is 0.25px thicker.
- The letter gets its own class for its 2.25px stroke. With the default
  stroke it looks too thin next to the border.
- The open link and editors arrow icons get a cta-icon class instead of
  the bare svg selector, and take the button color. The arrow used an
  icon-dropdown class with no rule.

AI-assisted-by: claude-opus-5-5

* ♻️ Use the DS stroke width and color in the subscription icons

The plan letter and the member icons keep the DS stroke width (the plan
letter line gets thinner), the member icons set their color instead of
the stroke, and the props copied from the button icon placeholder go.
The success dialog logo gets its own class instead of a bare svg
selector.

AI-assisted-by: claude-opus-5-5
2026-10-28 12:11:06 +01:00
Eva Marco
1109e38852
♻️ Use DS icons in the dashboard lists and placeholders (#12536)
* ♻️ Use DS icons in the dashboard lists and placeholders

The trash project options button becomes a ghost DS icon button, as in
the projects list, and says whether its menu is open. The search
placeholders, the empty project "create new file" button, the comments
placeholder and the legacy import library tag use the DS icons. The
legacy search icon had no fill rule, so its lens was filled black; the
DS one is not.

Two fixtures make the trash testable: a deleted project, and deleted
files due in 2100 (the existing ones expired in 2024, so the trash
dropped them). A visual test covers the trash row, the empty project
button, the project rename field and the comments placeholder.

AI-assisted-by: claude-opus-5-5

* 🐛 Remove the hidden close control from the dashboard rename field

The rename field of dashboard projects and files had a close control
next to the input that never showed: its legacy icon is drawn with a
stroke, but the styles only set a fill. It could not be reached with
the keyboard or a screen reader, and a click on it did what leaving
the field does: the blur saves the name first, and its handler saved
it too. It only left a pointer cursor over an empty spot. Enter, Escape
and leaving the field keep working as before. No visual change.

AI-assisted-by: claude-opus-5-5

* ♻️ Move dashboard trash, comments and rename styles to the DS

The trash, comments panel and rename field stylesheets resolve their
legacy color aliases, sizes, font sizes, mixins and the menu shadow to
DS tokens and values, and use logical properties. The comments panel
and the rename field no longer import refactor/; the trash keeps only
the shared dashboard header styles (common-dashboard), used by ten
dashboard modules.

Four selectors that the modules do not use are commented out with a
TODO. The comments dropdown z-index (4) has no DS token and keeps a
TODO. No visual change.

AI-assisted-by: claude-opus-5-5

* 🔥 Remove unused styles from the dashboard trash and comments

The SCSS refactor of the dashboard trash and comments left rules
commented out with a TODO because the module does not use them, or
because they target an svg inside a DS icon that is the svg itself. CSS
modules scope the classes to this module and no other source uses them,
so they go: .thread-group, .comments-icon-small, .add-file-btn,
.add-icon. No visual change.

AI-assisted-by: claude-opus-5-5

* 🐛 Use px2rem for the unread dot size in dashboard comments

The unread dot used $sz-8, which the DS sizes do not define, so the
integration bundle build failed. It now uses px2rem(8), the same 8px.

AI-assisted-by: claude-opus-5-5

* ♻️ Use DS typography and z-index in the dashboard comments

Review fixes for the dashboard lists styles:

- The comments dropdown title uses the headline-small typography. It
  was 11px regular, so it is now 12px medium (larger and bolder).
- The comments dropdown uses --z-index-dropdown instead of a raw 4.
- The rename field selector is the class alone; the input type is not
  needed to win over the input defaults (0px change).

The visual test now also captures the whole comments dropdown, so the
title change shows.

AI-assisted-by: claude-opus-5-5

* 🔥 Remove dead rules from the dashboard comments styles

The comments dropdown has no separators, so its hr rule matched
nothing, and the comments icon set the default stroke width. No visual
change.

AI-assisted-by: claude-opus-5-5
2026-10-28 12:10:47 +01:00