Phones previously got the desktop layout squeezed: ~12 system tabs plus one
tab per installed plugin wrapped into many rows of small pill buttons, and
the header's settings-search and system-stats widgets were dropped entirely
(hidden below their breakpoints, never relocated).
- Off-canvas nav drawer below md: the existing nav markup (system tab row +
#plugin-tabs-row, including dynamically injected plugin tabs) is wrapped in
a #site-nav container that CSS repositions into a slide-in drawer on small
screens. Same DOM nodes, same @click handlers, nothing duplicated. Tabs
become full-width rows with 44px+ touch targets. A hamburger button
(md:hidden) in the header and a backdrop toggle the new mobileNavOpen
Alpine state (added to both app() definitions, mirroring activeTab).
Clicking any tab, a search result, or the backdrop closes the drawer.
At md+ hard CSS guards make all drawer styles inert - desktop renders
exactly as before.
- Header widgets relocated, not hidden: placeHeaderWidgets() in app.js moves
the #settings-search-wrap and #system-stats nodes (same elements, listeners
intact - both are looked up by id from SSE/search code, so they must never
be duplicated) into the drawer below md and back into the header above it,
via a matchMedia listener.
- Fixed 13 breakpoint utility classes that templates referenced but app.css
never defined (sm:block, sm:grid-cols-2, sm:text-sm, md:block, md:w-auto,
lg:block, lg:flex, lg:w-64, xl:grid-cols-2/3, 2xl:grid-cols-2/3/4). This
was a live bug: 'hidden sm:block' on the search box and 'hidden lg:flex'
on the stats meant BOTH were invisible at every screen width. Audit method
(repeatable): diff classes used in templates vs defined in app.css.
- Mobile modal sizing: one global rule caps .modal-content at 95vw/90vh with
internal scroll below 640px - covers every modal without per-template
changes.
- Horizontal-scroll affordance: pure-CSS edge-fade shadows on
.overflow-x-auto containers (scrolling-shadows technique), plus larger
in-table touch targets below md.
Validation: breakpoint used-vs-defined audit now returns zero gaps; Jinja
parse of base.html passes; all changes to desktop behavior are additive
(new utilities) or scoped inside max-width media queries.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
Plugin config forms have been rendered server-side (plugin_config.html via
GET /partials/plugin-config/<id>) since the HTMX migration; the old
client-side generator survived as unreachable code. Verified dead by call
graph, not by naming: showPluginConfigModal and showGithubTokenInstructions
have zero callers anywhere in templates or JS, and everything removed here
is reachable only from those two roots.
Removed:
- plugins_manager.js: showPluginConfigModal, generatePluginConfigForm,
generateFormFromSchema, generateFieldHtml, generateSimpleConfigForm,
handlePluginConfigSubmit, the modal's JSON-editor view (initJsonEditor,
switchPluginConfigView, syncFormToJson/JsonToForm, saveConfigFromJsonEditor,
resetPluginConfigToDefaults, displayValidationErrors, closePluginConfigModal,
savePluginConfiguration, currentPluginConfigState), their exclusive helpers
(getSchemaPropertyType, escapeCssSelector, dotToNested, collectBooleanFields,
normalizeFormDataForConfig, flattenConfig, loadCustomHtmlWidget), the
orphaned-modal cleanup block, the modal's listener wiring, and the
never-invoked showGithubTokenInstructions/closeInstructionsModal pair.
- plugins.html: the #plugin-config-modal markup those functions drove.
- base.html: the deprecated pluginConfigData() component and the
window.PluginConfigHelpers shim (only ever called by pluginConfigData).
Deliberately kept, verified still live:
- renderArrayObjectItem, getSchemaProperty, escapeHtml/escapeAttribute
(window-exposed for the top-level array-of-objects handlers the
server-rendered form uses), toggleNestedSection, addKeyValuePair/
addArrayObjectItem families, executePluginAction, and
window.currentPluginConfig = null init (file-upload.js and
executePluginAction read it, optional-chained).
- app()'s internal generateConfigForm/generateSimpleConfigForm methods in
base.html: unreachable now but embedded in the live Alpine component;
excising methods from a live object is deferred to keep this change
zero-risk.
Validation: every deletion seam inspected line-by-line; Jinja parse of both
templates passes; repo-wide sweep confirms zero remaining references to any
deleted function or element id (deleted ranges contained no Jinja tags).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ