summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
2026-07-13Wavy-underline: allowlist to default; destroy-button and search polisherdgeist
Flips the plain-link underline from an opt-in list (growing every time a new context needed it) to the default a rule, with opt-outs living as more specific overrides where something shouldn't look like plain content -- .action_button, the icon nav, the search dropdown's bold results. The allowlist's own history made the case for this: three of its entries were already hand-copied duplicates of the same five properties, and two of those had already silently drifted. form.button_to's icon+text buttons (Destroy, currently) get the same inline-flex/gap treatment .action_button already had -- they were never included in that fix, so they still baseline-aligned an SVG against text instead of centering it, plus an explicit icon size they'd never had either. Dashboard search results: tags render as a wrapped row of pills instead of one-per-line, for visual distinction from page results. The public-preview link and its Revoke button get their own row rather than loose inline flow, which wrapped unpredictably next to a long token URL -- aligned by box-top rather than center or baseline, since a <button> doesn't reliably expose a text baseline the way a plain link does, with a small compensating offset for the button's own internal padding.
2026-07-13Fix search widget input width; remove dead navigation CSSerdgeist
The input's width was calc(100% - 70px), a number tuned to leave room for a "Search: " label that no longer needs to exist -- replaced by a placeholder instead, which also removes the fragile arithmetic entirely rather than re-tuning it. Also picked up a proper border-radius and padding, matching the dashboard's own search input. The width bug's actual proximate cause was a stray #navigation div { float: left } left over from the old text nav, interfering with the icon nav's own flex layout. Going through that whole block with the same scrutiny turned up four more rules worth removing, not fixing: a:hover (identical specificity, later in the file, silently winning the cascade since the icon nav landed), and #metadata/#overview_toggle in the remaining selectors were dead for two different, confirmed reasons -- #overview_toggle was the old wizard's toggle bar, gone along with it, and #metadata here was the pre-details-view subnav toggle, retired before this session and unrelated to the current, still-live #metadata id on the metadata-details section in nodes#edit (a confusing reuse that's caused real problems before).
2026-07-13UI cleanup pass: search overlay position, remaining table formserdgeist
The search overlay was positioned against the whole viewport (left: 50%, transform: translateX(-50%)) because it sat outside wider text nav it used to center under. Moved inside #navigation, which already has its own positioning context, so it now anchors to the icon nav itself instead of the page center. admin/search_results.html.erb was a bare, unstyled <ul> concatenating title and unique_name into one string by hand. Restyled to match the byline convention already used everywhere else (title_for_node, a separate link_to_path byline with the trailing arrow), and results now link to the node's admin show page rather than straight to edit, matching every other node list in the app instead of being the one exception. menu_items#new and sessions#new (the login page) were the last two views still using the pre-node_content/node_description table-based label/field layout -- found via a grep for the old td.description marker, not guessed at. Converting menu_items#new surfaced a real, separate gap: its input fields have never had a width rule, since the existing 690px rule only applies to a few specifically-named field IDs or anything inside #page_editor, neither of which applies here. Same likely gap probably exists on other simple CRUD forms (users/events/assets/occurrences/pages) -- not fixed here, flagged for its own pass rather than guessed at blind. nodes#new's Create button was pushed to the far right of the page by one stray class (node_content right) no sibling create form carries -- not a family-wide inconsistency, one extra class in one file.
2026-07-12Style the new dashboard; consolidate duplicated button/link CSSerdgeist
New rules for the icon nav, search bar, signpost/housekeeping button rows, and the two dashboard widgets. Generalizes .action_button from an anchor-only selector to cover any element, needed once Filter and Search became real buttons rather than submit_tag inputs coincidentally sharing similar values with the generic input[type=submit] fallback. Also folds three hand-copied duplicates of the wavy-underline "plain link" treatment (#flash a, div.pagination a, #menu_item_list td a) into the one shared rule they were always copies of -- two of the three had already silently drifted by missing the -webkit-prefixed property, the actual cost of copying instead of sharing rather than a hypothetical one.
2026-07-12Normalize action-button wording and add icons app-wideerdgeist
"New X" becomes "Create X" throughout (users, events, assets, menu items, nodes), matching the verb-first pattern the dashboard's own signposts already established, with a shared plus icon rather than a document-flavored one that only made sense next to "post". The "Destroy"/"destroy"/"Delete" family is normalized to "Destroy" everywhere, with a shared trash icon; occurrences#index and pages#index also pick up the destructive button class they'd been silently missing. Filter and Search convert from submit_tag to button_tag, the only way either can hold an icon alongside its label. Edit and the node editor's three dynamic labels (Continue Editing / Edit Draft / Lock + Edit) share one icon without touching their wording -- unlike Destroy's family, the state nuance in the text is real information, not just inconsistent phrasing.
2026-07-12Rebuild the admin dashboard: icon nav, search, signposts, widgetserdgeist
Replaces the old admin#index wizard -- accreted over years, never designed as a whole -- with the dashboard settled on this session: a three-icon nav (dashboard/search/log out, no locale selector), a nodes-first search bar, four task signposts, and two symmetric widgets (drafts/autosaves, recent changes) with a quiet housekeeping row beneath them. Node.recently_changed now filters and orders by the head page's own updated_at instead of the node's blanket timestamp, so a lock/unlock cycle with no actual publish no longer surfaces here, and the original publisher is no longer misattributed to someone else's housekeeping action. This also restores the "published" qualifier on each entry, which the query previously couldn't guarantee was true. @mynodes and its dedicated "My Work" table are retired along with the old wizard -- "Continue my work" is a link to the existing, already- correct NodesController#mine instead of a second, duplicate query. Its one dedicated test (dedup across multiple revisions by the same user) is ported to nodes_controller_test.rb, since mine already carries the same .distinct protection the old query did; it just had no test of its own until now.
2026-07-12Add a locale-aware relative-time helper; drop stale locale overrideserdgeist
Node.recently_changed and the new dashboard need "X ago" rendered correctly in both locales. Rails' own distance_of_time_in_words/ time_ago_in_words can't do this on their own -- the scope option changes which translation key is looked up, not the underlying grammar, and German's "vor" requires dative case, which is a different word form than the nominative plural Rails computes by default. relative_time_phrase/relative_distance is a small, from- scratch bucketing helper instead, with an explicit, hand-checked translation table per unit per locale. Also removes de.yml's datetime.distance_in_words and activerecord. errors blocks. Both used the old {{count}}/{{model}} interpolation syntax the current i18n gem no longer recognizes -- it silently leaves the literal placeholder text in the rendered output rather than erroring, which is why this went unnoticed until now. Both were shadowing rails-i18n's own current, correct translations for exactly these keys; deleting the local override lets the gem's version take over instead of maintaining a second, broken copy.
2026-07-12Add rails_icons (Tabler) for SVG iconographyerdgeist
Vendors the Tabler outline/filled icon sets via rails_icons' sync mechanism rather than a hand-rolled SVG set or a webfont -- this app had no icon system of any kind before. Icons are committed like any other static asset; re-syncing is only needed when adding an icon that isn't vendored yet, via the new `make sync_icons` target.
2026-07-12Add a grouped tag+node search endpoint for the upcoming dashboarderdgeist
AdminController#dashboard_search returns tags and nodes as separate, labeled groups (via ActsAsTaggableOn::Tag.named_like and Node.editor_search) rather than the flat per-node list every existing picker returns. initSearchPicker gains a renderResults callback option that, when given, replaces the default per-node rendering entirely -- lets the dashboard render its two labeled groups without a second, parallel picker implementation. resultsHeaderHtml (the "Press Enter to see all results" hint) is now threaded through as a third argument to renderResults, so a picker with custom rendering can still show it -- previously only the default per-node branch ever did. Tags link straight to the existing /admin/nodes/tags/:tag view rather than becoming an in-place filter chip.
2026-07-12Extract drafts/recent query logic from controller into Nodeerdgeist
Node.drafts_and_autosaves and Node.recently_changed replace inline query logic in NodesController#drafts/#recent -- pure refactor, no behavior change for either action. The real reason: the dashboard's upcoming abridged widgets need the exact same queries the full pages already use, just limited and (for drafts) sorted with the current user's own locked nodes first. Better to share one method than let a widget and a full page quietly drift onto two versions of "what counts as a current draft." current_user_id: is an explicit, optional argument rather than a separate method -- ordering by lock ownership only applies when a user is given; omitted entirely, it's pure recency, exactly today's behavior. Needed Arel.sql wrapping a sanitized CASE expression before .order would accept it -- sanitize_sql_array only vouches for the values inside the string, a separate Rails safety check still refuses any string shaped like more than a plain column reference unless it's explicitly marked as already-vetted.
2026-07-12Unify the four field-level pickers, fix search visibilityerdgeist
menu_items/parent_search/move_to_search/event_search each duplicated their own debounce-free AJAX call and result rendering. Replaced with one shared initSearchPicker, taking each picker's distinctive behavior (parent_search's live path preview, event_search's title hint) as a callback rather than a whole reimplementation. Adds a real debounce with an out-of-order-response guard -- none of the four had either before. admin_search (the Alt+F top-bar search) now shares the same function via its own url/isActive/header options, gaining the same guard and fixing an inconsistency of its own (it previously always slid its panel open, even on zero results). Each picker's results container gets its own id instead of sharing was ever supposed to be on screen at once, an assumption with no actual enforcement behind it. Styling moved from an id-pair (#menu_search_results, #search_results) to a shared .search_results class so a future picker never needs this file touched again. menu_search and the admin top-bar search now call Node.editor_search instead of the public, head-only Node.search -- both are admin-only, authenticated views, and had no reason to inherit the public search's "can't find a draft" limitation. The always-ignored :per_page => 1000 on the latter is gone too; Node.search's second argument was never read by the method at all. Also removed a stale #metadata a { text-transform: lowercase } rule, found while verifying the above -- written for the pre-subnav-removal expand-toggle, which no longer exists; it had been silently lowercasing nodes#edit's own, unrelated #metadata div (including move_to_search's results) by id coincidence ever since. #main_navigation and #overview_toggle intentionally left capitalized rather than special-cased -- both belong to the nav bar already slated to shrink to three icons, not worth polishing on the way out.
2026-07-12Give the sitemap its own view, with collapse and descendant countserdgeist
Extracted from admin#index's inline table into NodesController#sitemap. Nested <details>/<summary> per branch, one linear pass over the existing flat [node, level] list (no added queries) -- each node's own descendant count computed the same way, via a small stack rather than re-walking the tree per node. Branches under updates/, club/erfas, club/chaostreffs, and disclosure start collapsed by default (CccConventions::SITEMAP_COLLAPSED_PATHS); any branch currently collapsed, whether by that default or because someone just closed it, is highlighted via a plain :not([open]) selector -- no state tracked outside the DOM itself. Dropped the update?-post exclusion this view used to rely on -- no longer needed now that updates/ collapses instead of being filtered out, so its real children (previously silently absent) now show up correctly. admin#index's own, separate @sitemap query is unchanged; that view has no collapse mechanism to compensate and wasn't part of this.
2026-07-12Removing a test script that was accidentally committederdgeist
2026-07-11Add drafts/recent/mine/chapters admin node viewserdgeist
Four NodesController actions -- drafts, recent, mine, chapters -- each building its own base scope, sharing one private method (index_matching) for search narrowing and pagination. Wizard rewrite to link into these instead of rendering its own tables is a separate, later step. Node.editor_search backs the shared "q" narrowing: an ILIKE substring match against title/abstract on whichever of head or draft is present, splitting the term on whitespace and requiring every word to match somewhere independently, not as one phrase, since real words can end up separated by markup in the underlying HTML. Deliberately separate from Node.search, the public content search, which stays tsvector-based and head-only. chapters generalizes into /admin/nodes/tags/:tags for an arbitrary tag list (OR'd, not AND'd), sharing the controller action but rendering its own template rather than branching inside one view.
2026-07-11Make page_translations' search trigger self-installingerdgeist
search_vector is populated by a raw Postgres trigger created via execute() in a migration -- invisible to schema.rb, which only represents structure Rails understands. Any database rebuilt from schema.rb rather than replayed migrations (test, a fresh db:setup, disaster recovery) silently lost full-text search entirely, with no error -- NULL @@ anything is never true in Postgres. Page.ensure_search_vector_trigger!, called from an after_initialize initializer, reinstalls the trigger via CREATE OR REPLACE on every boot, in every environment. Idempotent and cheap.
2026-07-11Move all admin routes under /adminerdgeist
Nodes and revisions join assets under the existing /admin scope. admin#index/search/menu_search/conventions moved into the same scope block instead of each manually prefixing "admin"/"admin/...". Route helper names and behavior unchanged -- only the URLs move.
2026-07-11Make /nodes/parameterize_preview route independenterdgeist
2026-07-11Do not offer to destroy the only draft of a never-published nodeerdgeist
2026-07-11Style admin pagination widget, add missing locale keyserdgeist
Pagination had almost no styling, and its current-page selector never matched will_paginate's actual markup. Added chip-style page links, a solid current-page indicator, and distinct disabled states for Previous/Next. Previous/Next/gap rendered as empty, unlabeled elements -- will_paginate only ships English translations and this app defaults to German. Added previous_label/next_label/page_gap to en.yml and de.yml.
2026-07-10Prevent a staged parent move from crashing on its own descendanterdgeist
publish_draft! called move_to_child_of before validating the new parent at all. Staging a node under one of its own descendants created a real, if transient, cycle in parent_id the instant that save landed -- and update_unique_names_of_children's after_save callback, which recurses down through parent_id with no cycle check and no depth limit, bounced between the two nodes forever, crashing the whole process with a SystemStackError rather than just producing bad data. Now rejected up front with a normal ActiveRecord::RecordInvalid.
2026-07-10Fix current_user/uniq inconsistencies in admin#index, remove dead queryerdgeist
@mynodes used the bare @current_user ivar instead of the current_user method (works today only because login_required already forces that memoization; every other query and controller in this app uses the method), and .uniq, which is plain Enumerable#uniq here, not Relation#distinct -- it materializes every joined row into an Array before deduplicating, rather than deduplicating in SQL. @mypages had the same ivar pattern, no .order/.limit unlike every sibling query in this action, and -- confirmed via a full grep -- is referenced nowhere else in the codebase. Removed rather than bounded; nothing was ever rendering it.
2026-07-10Add the missing assertions two tests were silently running withouterdgeist
Both ran real code and checked nothing -- Minitest's "missing assertions" warning has been firing on every run this session. test_find_or_create_draft_if_draft_exists_and_is_owned_by_user called the method twice but never checked what came back; now confirms the second call returns the same draft rather than creating a new one, and leaves the lock and page count untouched. test_destroy_a_published_node destroyed a node and loaded the admin index but never checked the response, unlike its two neighbors covering the same destroy path (dangling pages, orphaned occurrences). Added the assert_response :success its own shape already implied. No behavior changed -- both were passing before for the same reason they're passing now, they just weren't proving anything.
2026-07-10Fix rrule/url column overflow on events#indexerdgeist
Long RRULEs previously overflowed their column with no wrap point; now escaped and rendered with a <wbr> after each semicolon, so a long rule wraps at a clause boundary instead of running off the table. Deliberately not truncated -- a cut-off RRULE's trailing clause (BYDAY, BYMONTH, etc.) is usually the most specific part. The url column is now a real link, truncated with an ellipsis at a fixed width -- deliberately no tooltip, since hovering a real link already shows the full address in the browser's own status bar. rrule_with_break_opportunities splits on the raw string's own semicolons before escaping each piece, not after -- escaping first and searching the result for semicolons also matches the ones inside &lt;/&gt; entities, corrupting anything containing a literal < or >.
2026-07-10Fix page-editor text inputs falling through to unstyled defaultserdgeist
"#page_editor form input[...]" -- but form_for wraps around inside, so the selector never matched anything. Every field without its own dedicated per-id rule (i.e. everything outside nodes#edit) fell through to the bare, unsized base rule instead. Fixed by dropping the erroneous "form" and adding textarea, which the rule never covered either even for its intended targets. nodes#edit's own per-id rules (#page_title, #page_abstract, etc.) are unaffected -- same specificity, later in the file, so they still win where they already applied.
2026-07-10Add test for backlink in revisions#indexerdgeist
2026-07-10Add backlink to node from revisions#indexerdgeist
2026-07-10Include assets, tags and template in diff between revisionserdgeist
2026-07-10Fix docstring for the aggregate shortcodeerdgeist
2026-07-10Stop diff output from corrupting the document on structural tag changeserdgeist
sdiff aligned an inserted paragraph break correctly, but the renderer wrapped the raw </p>/<p> tokens in <ins> regardless -- invalid nesting that browsers silently repair by dropping the unmatched closing tag, leaving everything downstream rendered inside an <ins> with nothing left to close it. Same failure mode as the retired cacycle_diff.js, different cause. Tag tokens now render as a plain-text pilcrow with a tooltip naming the actual tag, never as literal markup inside <ins>/<del>. Applies to both inline and side-by-side. Side-by-side's new panel flattening a real paragraph break into a marker, rather than showing its own true structure, is a known, accepted trade-off for now -- not the same problem, and not fixed here.
2026-07-10Restore the inline/side-by-side toggle for named-layer diffserdgeist
Hiding the numeric revision picker for head/draft/autosave comparisons accidentally took the view toggle down with it -- it was nested inside the numeric-only branch instead of being its own control. Now renders unconditionally as plain links, with the current mode shown as non-interactive text. Switching which revisions to compare via the numeric form no longer silently resets the view mode back to inline.
2026-07-10Ensure that comparison view toggle is there for all diff modeserdgeist
2026-07-10Remove dead CSS rule for table.node_table th.titleerdgeist
2026-07-10Move link to full revision list out of summaryerdgeist
2026-07-10Destroying Draft or Discarding Autosave drops you where you lefterdgeist
2026-07-10Add head/draft/autosave layer comparison at three UI entry pointserdgeist
Node#resolve_page_reference and #available_layer_pairs let Page#diff_against compare named layers (head/draft/autosave), not just numbered revisions -- autosave was never part of Node#pages, so this was the missing piece. Wired into nodes#show's Status section, nodes#edit right after an autosave gets resurrected ("What changed?"), and the admin wizard's current-drafts table, which now also lists autosave-only nodes it previously never showed. revisions#diff hides the numbered-revision picker when comparing named layers (it can't represent them), shows a plain label instead, and offers buttons to switch between whichever other pairs make sense for the node's current state. Destroying the topmost layer is available directly from the diff view, reusing the existing revert! path. "Discard changes" is renamed "Discard Autosave" everywhere it appears, to match "Destroy Draft".
2026-07-10Whitespace fixeserdgeist
2026-07-10Fix stale assertions and escaped body in revisions#showerdgeist
The table-based markup revisions#show's own test was asserting against (<strong>, <td>) was retired when this view moved to the node_description/node_content pattern; updated the assertions to match. Found in the same pass: @page.body had no raw(), so any real markup in a revision's body rendered as escaped entities on this page — same bug class as revisions#diff, just not yet fixed here.
2026-07-10Retire vendored cacycle_diff.js for a diff-lcs-based diff viewerdgeist
Revisions#diff now computes an inline or side-by-side word diff server-side (Page#diff_against, lib/html_word_diff.rb) instead of shipping raw/escaped content to the browser for a 2008-era vendored JS differ to mangle. View mode is picked via a `view` param, settable either on the diff page itself or directly from the revisions#index sticky bar next to "Diff revisions". Also fixes a pre-existing bug in RevisionsController#diff's single- revision branch, which set params[:start]/params[:end] instead of params[:start_revision]/params[:end_revision].
2026-07-09Move subnav items to action barerdgeist
2026-07-09Remove subnavigation after the last consumer has goneerdgeist
2026-07-09Modernize events#edit: node re-link guard, editable Title, layout overhaulerdgeist
Converts the <p>-per-field layout to node_description/node_content, matching every other converted view. The RRULE builder's internal markup and JS-driven show/hide are untouched -- only its containing block moved. Adds the same node picker and optional Title field events#new just got, with one addition specific to edit: an already-linked event shows its node as a plain link, with the picker itself gated behind a <details> disclosure. Re-linking a live event to a different node is rare and has real side effects (which page it appears under, which tag-prefill applies), so it gets an extra click rather than sitting as easy to trigger as the unlinked case, which stays unguarded. Moves Destroy out of the subnav and into the page body, matching events#show. Required extracting it (and Back) into their own block outside form_for -- button_to generates its own nested <form>, which the browser silently drops when nested inside another; this is the same failure mode nodes#edit hit earlier in this branch. Reuses the existing .node_action_bar class rather than the naked block that would've resulted, adding a "standalone_action_bar" modifier for the two things specific to living outside #page_editor: the -125px margin every other line in the form gets for free, and link underline visibility, since this is the first .node_action_bar instance with a bare <a> rather than only buttons.
2026-07-09Rework events#new with the new layout paradigmerdgeist
2026-07-09Add an events view for events without node, part 2erdgeist
2026-07-09Add an events view for events without nodeerdgeist
2026-07-09Modernise revision#show view with the paradigms established in all the other views, removing subnaverdgeist
2026-07-09Paginate Events indexerdgeist
2026-07-09Remove subnav from nodes#index, New node has its own action button nowerdgeist
2026-07-09Fix malformed-HTML fallout across RSS, link rewriting, and flash messagingerdgeist
Editors' TinyMCE output can contain unclosed void elements like <br>, which is valid HTML5 but invalid XML -- three different places assumed the stricter rule and broke or silently misbehaved on the looser one. The Atom feed's <content type="xhtml"> block required real, well-formed XML structure but was handed a raw, unescaped body string; switched to type="html" with CGI.escapeHTML, matching how title/summary already handle the same content. rewrite_links_in_body used libxml's strict XML parser to rewrite internal links to be locale-prefixed, which raised on exactly this class of malformed markup -- silently, since the whole method was wrapped in rescue; nil, meaning the link rewrite (not the save) quietly failed with no error anywhere. Replaced with Nokogiri's lenient HTML parser, which repairs malformed void elements rather than rejecting them; also drops the bare rescue now that the actual failure mode it was guarding against shouldn't occur, and fixes two adjacent bugs found while in this method: a typo'd /sytem/uploads/ regex that could never match, and a missing https:// exclusion alongside the existing http:// one. Also addresses stale flash messaging surfaced while testing the above: update's save confirmation was being clobbered by edit's own "locked and ready" notice on the very next request, since nothing distinguished a fresh lock acquisition from a redirect back after saving. The save confirmation now names the next step (publish from Status) and flags a stale translation if one exists, using Page#outdated_translations?, already present but previously unused by any controller.
2026-07-09Major resource view cleanuperdgeist
Remove dead pages#index and occurrences resource; fix menu_items#new menu_items#new called menu_item_params, which requires a submitted menu_item key that can never be present on a fresh GET -- new only ever needs a blank record to render against. pages#index and the entire occurrences resource were unmodified Rails scaffold generator output, unlinked from any nav and unreachable except by direct URL. pages#index had no controller action at all (@pages was never assigned); occurrences#index queried every row with no scoping or pagination and its layout referenced a scaffold.css asset that no longer exists in the pipeline. Neither is a real editorial surface -- occurrences are auto-generated from an event's RRULE and were never meant to be browsed as a flat list. Removed rather than repaired. pages#preview and pages#sort_images remain, now as explicit routes rather than under a full resources :pages block.
2026-07-09Fix button height mismatch between bordered and pill button styles, extend table styling to assets/users/eventserdgeist
Fix button height mismatch, extend table styling to assets/users/events a.action_button (bordered) and the state_changing/destructive/computation pills (borderless) rendered at different heights, since each pill variant set its own padding at higher specificity than the shared rule meant to equalize them. Padding and border-radius now live only in one shared rule; also restores the #page_editor prefix on a.action_button, needed to outrank the #page_editor a wavy-underline rule. table.assets_table, table.user_table, and table.events_table now share table.node_table's border-collapse/header/hover treatment. tr min-height is dropped -- browsers don't honor height on <tr> -- in favor of cell padding, kept at zero for node_table (spacing already comes from its h4/p content) and set to 8px for the plain-text tables.