summaryrefslogtreecommitdiff
path: root/app
AgeCommit message (Collapse)Author
5 daysRemove subnavigation after the last consumer has goneerdgeist
5 daysModernize 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.
5 daysRework events#new with the new layout paradigmerdgeist
5 daysAdd an events view for events without node, part 2erdgeist
5 daysAdd an events view for events without nodeerdgeist
5 daysModernise revision#show view with the paradigms established in all the other views, removing subnaverdgeist
5 daysPaginate Events indexerdgeist
5 daysRemove subnav from nodes#index, New node has its own action button nowerdgeist
5 daysFix 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.
5 daysMajor 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.
5 daysFix 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.
5 daysRestyle revisions#index: sticky diff bar, computation button style, pre-selected defaultserdgeist
Adds a fourth button category, .computation -- teal, matching Preview's existing color -- for actions that compute a view without changing state, the first case in the app that didn't fit reversible/state-changing/ destructive. Diff revisions now starts disabled and only enables once two distinct revisions are selected, matching the disabled-state pattern already used for Unlock + Back. The header row is wrapped in a real <thead> (previously flat inside the table alongside data rows) so a sticky diff bar can sit above the fold on long revision lists -- selecting a comparison pair no longer requires scrolling back up to find the button, or back down to confirm what's selected, both surfaced live via one shared JS function. The two most recent revisions are pre-selected by default, covering the most common comparison (current against previous) with no extra clicks. Deliberately scoped to numbered revisions only -- comparing draft/head or autosave/draft was raised and set aside, since neither exists as a row in this table by design and extending the comparison to them is a separate feature, not a default on this one. Also drops the dead "Edit" subnav link, redundant with nodes#show's Status section covering the same action with state-aware labeling this static link could never have.
6 daysAdd the Status section to nodes#show and rebuild nodes#edit's action barerdgeist
Retires content_for :subnavigation on nodes#show entirely -- Preview was fully redundant with Links' own preview URLs in every state, and Edit is now a permanent, always-rendered action inside the new Status section rather than a link floating at the top, consistent with the "action lives next to the info" pattern People already established for Unlock. Status surfaces head/draft/autosave plainly and gates Edit/Publish/Destroy/Discard on lock ownership specifically, not mere lock presence -- @node.locked? alone would have blocked the lock owner's own session, caught by the click-test and fixed here rather than shipped. nodes#edit's action bar is rebuilt to sit outside form_for (both button_to calls render their own nested form, invalid HTML the browser was silently stripping) with Save wired back in via form="..." rather than needing to live inside the form tag at all. Also brings the locked-and-ready-to-edit flash message, and the visual polish from this session's click-test: consistent button heights across the bordered and pill-shaped variants sharing a row, spacing between Status's data and its actions, and error_messages styling reusing the destructive-red vocabulary already established elsewhere.
6 daysFix revert being unusable from an unlocked nodes#showerdgeist
revert! requires the lock to already be held, correct for nodes#edit where the editor holds it throughout -- but both exclusive-case buttons on nodes#show fire from a node that starts out unlocked, which raised LockedByAnotherUser incorrectly. lock_for_editing! first makes both call sites safe: a harmless re-stamp when already the owner, a real but momentary acquisition otherwise, released again by revert! itself once nothing is left to protect.
6 daysFix blank-title validation and its lost form state on re-rendererdgeist
A blank title failed presence and length validation simultaneously, showing two redundant messages for one problem; length now skips whenever slug is already blank, matching presence's own skip condition. Separately, create's failure path never computed @selected_kind/@parent_id/@parent_name the way new does, so re-rendering after a validation error silently lost which kind and parent had been chosen -- for the auto-tagging/auto-templating kinds, that meant a corrected resubmission could silently produce a plain generic node instead. required on the title field closes the common case without a round trip; the server-side fix remains the actual guarantee, since required is trivially bypassed.
6 daysAdd a action bar to the node edit view. This is the first application of a proposed new layouterdgeist
6 daysAdd revert!/discard and rebuild nodes#edit around the new hierarchyerdgeist
revert! discards exactly the topmost non-empty layer -- autosave if present, else draft -- and reveals whatever's beneath it, releasing the lock only once nothing is left to protect. Guards against destroying a brand-new, never-published node's only draft, which would violate the (head | draft) invariant every other method here assumes holds; the view's Destroy/Discard button is gated the same way. nodes#edit now calls lock_for_editing! instead of find_or_create_draft, and always displays autosave || draft || head, resurrecting an abandoned session's unsaved content by default with an explicit flash explaining what's shown and how to get back to the last saved version. The view drops content_for :subnavigation entirely: Show becomes "Unlock + Back", Preview stays a plain link, metadata's own <details> already replaced the old toggle, and Publish moves off this page for good, per the earlier decision to manage the publish lifecycle entirely from nodes#show. Save Draft and Save + Unlock + Exit appear both above and below the form, given the body field alone runs 600px on desktop.
6 daysFix authorship and published_at loss in autosave promotionerdgeist
Two real bugs surfaced by the full test suite, not by the new tests written for this feature -- both were latent the moment lock_for_editing! and save_draft! replaced find_or_create_draft, and only visible once an existing test exercised the exact path each one lived in. lock_for_editing! never stamped user/editor onto a draft that already existed when the lock was acquired. find_or_create_draft used to do this as part of acquiring the lock; splitting lock acquisition from draft creation dropped it entirely, since it looked like draft-creation logic rather than locking logic. Restored as an explicit step: claim authorship only if none is set yet, editorship unconditionally, matching the old behavior exactly. save_draft!'s "no draft yet" branch set user/editor before calling clone_attributes_from, whose first line is an unconditional self.reload -- silently discarding both, since neither had been persisted yet. clone_attributes_from also always copies published_at from its source without the ||= guard used for template_name, and the source here is always the autosave, whose published_at is never anything but nil -- meaning every single promotion, not just the first, was quietly resetting a published page's published_at, which publish_draft!'s own ||= Time.now would then treat as never-published and re-stamp. Fixed by running clone_attributes_from first on both branches, then applying user/editor/published_at afterward, exactly as the existing-draft branch already happened to do by accident. Four controller tests updated to insert a real put :update between get :edit and assertions that used to be true immediately after visiting edit -- deferred draft creation means edit alone no longer produces one, which is the intended consequence of this whole redesign, not something these tests were meant to catch.
6 daysAutosave posts to its own endpoint surface lock loss immediatelyerdgeist
Previously piggybacked on the main form's action and relied on a .js.erb response to update #flash; now posts to the dedicated autosave route with an explicit _method=put override, and reacts to a 423 by stopping the timer and showing a persistent banner rather than letting the next Save discover the conflict on its own.
6 daysFix tests for the new autosave routingerdgeist
Route Save and autosave through the new Node methods update now promotes the current autosave into the draft via save_draft! rather than writing submitted params directly; autosave gets its own PUT member route so PATCH update can mean "deliberate save" specifically, matching every other custom action on this resource. Both now require the lock to already be held rather than acquiring it themselves, which is a deliberate narrowing: through the real UI this is always true, since neither can fire before edit has loaded. Two existing tests called update directly with no prior edit, which the old code tolerated by acquiring the lock as a side effect; updated to call edit first instead of loosening the guarantee back open. NodesController#edit itself is unchanged and still calls the old find_or_create_draft, so drafts are still created eagerly on entering edit for now -- switching that over is the next step, not this one.
6 daysAdd head/draft/autosave hierarchy to Nodeerdgeist
Introduces autosave_id as a third, unversioned layer above draft/head, with lock_for_editing!, autosave!, and save_draft! as the new entry points. Also fixes a real bug in wipe_draft!: its "no draft" branch unconditionally released the lock, which was safe when "no draft" only ever meant "nothing is happening" — no longer true now that a lock can exist with only an autosave beneath it. lock_for_editing! deliberately does not call wipe_draft! at all, for the same reason: an intruder calling it while a lock was genuinely held would otherwise silently steal it via wipe_draft!'s own unlock side effect, caught by the new two-user lock test.
6 daysPromote page title to headline in nodes#showerdgeist
6 daysTuck away metadata section in nodes#edit behind a summary/detail cascadeerdgeist
6 daysWIP: dynamic rrule buildererdgeist
6 daysMake sure that page title actually is saved through to ORMerdgeist
7 daysFix subheadings in admin wizard viewerdgeist
7 daysFix column count between table header and bodyerdgeist
7 daysAdd or fix class names for the link underline rules to matcherdgeist
7 daysAdd class destructive to destructive buttonserdgeist
7 daysAdopt button pills in viewserdgeist
7 daysReorganize admin.css and overhaul button/link/flash visual hierarchyerdgeist
Sorts rules by hierarchy (base -> layout -> navigation -> buttons -> tables -> forms -> components), consolidates three duplicated media query breakpoints into one each, and adds section comments. Bundled with this reorganization, since the file was rewritten wholesale: - form.button_to.state_changing: new tinted-pill variant (blue) for Publish/Restore/Unlock/Revoke, matching .destructive's pattern - form.button_to.destructive: now a tinted pill at rest instead of plain colored text, consistent padding at rest and on hover - Scoped wavy-underline link-visibility fix for #page_editor, table.node_table, table.assets_table, table.events_table, table.user_table, .add_child_links, and the dashboard draft list - #flash decoupled from the page's structural nav-to-content spacing (.admin_content_spacer), which the flash div was silently providing as a side effect whenever it rendered
7 daysRemove stale public_bundle.js reference from asset manifesterdgeist
7 daysAdd context-aware child-creation shortcuts to nodes#showerdgeist
parent_match Procs on CccConventions::NODE_KINDS, matched against unique_path, decide which "add child" kinds show on a given node. Fixes nodes#new not honoring a pre-selected kind (radio group and parent-field visibility both defaulted to "generic" unconditionally).
7 daysReplace awesome_nested_set with a plain parent_id-based NestedTree concernerdgeist
Root-caused this session: appending a child to any node never widened that parent's own rgt boundary, on the pinned revision (Gemfile tracked main directly, chasing a too-conservative gemspec constraint - not, as first assumed, a deliberate pin to avoid a known bug). Reproduced cleanly on a single ordinary create with no concurrency and no bulk operation involved, confirmed via the gem's own SetValidator, then confirmed as the root cause of nodes_controller_test.rb's 3 long-standing "pre-existing" failures - not three separate mysteries, one bug. admin_controller's sitemap needed its own real conversion, not just a drop-in: awesome_nested_set's lft column implicitly provided correct depth-first tree order for free, which the old code combined with a separate class-level each_with_level iterator. Both replaced by one method, self_and_descendants_ordered_with_level, computing an ordered [node, level] list in a single query-then-walk pass - checked against the actual view template first (admin/index.html.erb) rather than assumed, since it relies on list order alone to render correct visual nesting. lft/rgt/depth columns intentionally left in schema, unused - dropping them is a separate, deliberately deferred migration once this is proven running for a while, not bundled with the behavior change.
8 daysAdd tree-position scoping to Page.aggregateerdgeist
New :children option ("direct" or "all") on the [aggregate] shortcode, composable with the existing :tags filter - both apply as independent successive .where clauses, ANDing together automatically, no special casing needed. content_helper.rb passes the current @page.node through as :node whenever :children is requested, since Page.aggregate has no way to resolve "which node" from the shortcode's bare option strings on its own. Guarded so nothing changes for any existing [aggregate] shortcode that never uses children= - :node stays nil, the new branch never fires. "all" resolves via node.descendants.pluck(:id) rather than embedding the descendants relation directly as a subquery. Not proven strictly necessary - extensive debugging this session initially pointed at a subquery/table-alias collision, but every actual failure traced instead to accumulated test-node debris left in the dev DB by earlier interrupted runs, confirmed by re-running against a cleaned database. Kept anyway as a one-round-trip-cheaper, more defensive default regardless.
8 daysApply slug immediately for never-published nodeserdgeist
staged_slug existed to protect a live public URL from changing until an editor explicitly publishes - correct once a node has a head, but nodes_controller#update wrote to it unconditionally, so a brand-new node being renamed before its first-ever publish showed a stale slug in previews (e.g. nodes#new's resulting-path preview) that silently diverged from what would actually go live. Node#staged_slug= now applies directly to slug when head is blank - nothing is live yet, so there's nothing to protect. Once head is present, defers to staged_slug exactly as before. Verified both paths directly: a never-published node's slug now updates immediately, and an already-published node's rename still stays deferred until the next publish, unchanged from existing behavior.
8 daysRestructure nodes#show and nodes#new around node_description/node_contenterdgeist
nodes#show's flat <table> of label/value pairs grew unreadable once Admin Preview and Public Preview were added alongside Public Link - switched to the div.node_description/div.node_content pattern already used by nodes#edit, grouped into People/Dates/Links/Revisions/Tags/ Events/Children sections rather than one undifferentiated list. People/Dates/Links use a flex-based item layout, not a second <table> - multiple independent tables sharing one outer width but different column counts produced misaligned columns with no shared grid. Flex items size to their own content with a shared minimum instead. Revisions and Children are both collapsed via <details>, no JS needed - previously nodes#show didn't list a node's children at all, blocking the ability to find/navigate erfa and chaostreff pages without already knowing they're now standalone nodes. Revision list items link to node_revision_path(@node, page) - the actual per-revision diff/restore view, not the plain index. nodes#new converted to the same pattern for visual consistency, plus a few things surfaced along the way: submit buttons were unstyled site-wide (a bare input[type=submit] with no border, fill, or hover state - Create on this exact page was easy to miss entirely), fixed with a bordered/bold treatment reusing the existing form.button_to visual language rather than introducing a fourth button style. Title regained the bold weight it had before the table rewrite dropped it silently. Tag list grouping and full link/button semantic taxonomy (show vs edit/add vs publish/revoke vs destroy, applied consistently across every link on the page, not just buttons) are known follow-ups, not attempted here.
8 daysFix shared preview links breaking after a second publisherdgeist
A token's page could stop being node.head_id (superseded by a newer draft) while still being published_at.present? - the old check only compared against the current head, so a superseded page fell through to direct rendering instead of redirecting, serving a stale frozen snapshot indefinitely instead of the current live content. Also handles scheduled publishing correctly: a page can be head_id but not yet public? (published_at in the future) - that case must still render directly, not redirect into a 404 on the not-yet-live public URL.
8 daysAdd public preview links for draftserdgeist
Lets editors share a draft with people outside the admin system, via a random per-page token rather than an enumerable id - /preview/<token>. shared_previews#show is intentionally unauthenticated. Redirects to the real public URL once the page is published. Surfaced on nodes#show (Admin Preview + Public Preview, next to Public Link) as generate/revoke buttons.
8 daysAdd preview_token to pageserdgeist
New column + unique index, plus ensure_preview_token!/revoke_preview_token! on Page. Generated lazily (only when explicitly requested) rather than via has_secure_token's default auto-generate-on-create, so a live, shareable secret isn't silently minted for every page ever created.
8 daysRemove dead custom_rrule references after column droperdgeist
events#index still read event.custom_rrule - a live bug the column-drop migration introduced, not just stale test data. Caught by four test failures (three fixtures/direct attribute hashes still setting the removed column, one UnknownAttributeError from a stale fixture loaded before any test method runs), none of which were actually testing this view - "should get index" passed throughout with zero Event records present, meaning it could never have caught a per-row rendering bug. Strengthened to create one real event first, so a future stray reference to a dropped or renamed column fails loudly instead of silently passing on an empty table.
8 daysAdd erfa/chaostreff node-kind creation conventions, part 2erdgeist
The following changes were already announced in the last commit, but the files forgotten. Here's them actually attached. lib/ccc_conventions.rb: NODE_KINDS registry replaces the kind-specific branches previously scattered across nodes_controller#create (tags), unique_path-position check). Each kind declares its parent lookup (a Proc - Update's "update"/"press_release" continue to genuinely find-or-create their year-folder via Update.find_or_create_parent; erfa/chaostreff use a plain find_by_unique_name!, since their parent nodes are fixed and a missing one should fail loudly, not be silently created), its tags, and (new) its default template. New read-only admin/conventions view dumps the NODE_KINDS registry as a reference table for whoever's confused later about how a given kind behaves - deliberately just renders the same label/hint text used on nodes#new rather than re-describing parent/tags/template separately, since decomposing the parent Proc would mean either invoking it (Update's has a real side effect - creates the year folder) or re-stating its logic a second time in a different shape, either of which risks drifting from the actual behavior.
9 daysWhitespace fixeserdgeist
9 daysSurface node-kind conventions in the admin UIerdgeist
nodes#new: each kind's radio button now shows a label plus a smaller, muted .field_hint explaining what happens automatically (parent, tags, template) - previously a single undifferentiated string mixing a short label with a long parenthetical, cluttering the smaller kinds (erfa/chaostreff) worst. Title field gets the same hint treatment, telling editors up front that the slug is auto-generated and where to adjust it later - the previous silence here meant editors only discovered slug generation after already committing to a title, with no visible way to fix it. New "Resulting path" row live-previews the full URL (origin + kind's path prefix + a debounced call to the new parameterize_preview route, reusing the exact same slug_for helper "create" now calls, so the preview can never diverge from what actually gets created) as the editor types, plus a copy-to-clipboard button. For "generic", the prefix comes from whichever parent gets chosen via the search widget, not the registry - parent_search.link_closure now stashes the chosen node's unique_name as a data attribute for exactly this. New read-only admin/conventions view dumps the NODE_KINDS registry as a reference table for whoever's confused later about how a given kind behaves - deliberately just renders the same label/hint text used on nodes#new rather than re-describing parent/tags/template separately, since decomposing the parent Proc would mean either invoking it (Update's has a real side effect - creates the year folder) or re-stating its logic a second time in a different shape, either of which risks drifting from the actual behavior. nodes#edit's Template field gets a one-line hint noting the value may already be pre-filled based on how the node was created - the one place the previous commit's new inheritance mechanism actually surfaces on this view.
9 daysAdd erfa/chaostreff node-kind creation conventionserdgeist
lib/ccc_conventions.rb: NODE_KINDS registry replaces the kind-specific branches previously scattered across nodes_controller#create (tags), unique_path-position check). Each kind declares its parent lookup (a Proc - Update's "update"/"press_release" continue to genuinely find-or-create their year-folder via Update.find_or_create_parent; erfa/chaostreff use a plain find_by_unique_name!, since their parent nodes are fixed and a missing one should fail loudly, not be silently created), its tags, and (new) its default template. Node gains a default_template_name column (migration, with backfill for existing update-tree nodes via node.update? - reusing that method rather than re-deriving its unique_path check in raw SQL). Page#set_template now inherits from node.default_template_name, falling back to the old update?-based check only when the column is blank, and only fills in template_name when nothing's already been explicitly chosen - a deliberate change from the previous behavior, which unconditionally overwrote template_name on every save regardless of manual selection. Node#update? itself is unchanged and still used as-is by admin_controller's sitemap filtering - a genuinely different, still valid use of that check. "generic" stays special-cased in the controller, parametrized by params[:parent_id] at request time - doesn't fit "kind implies a fixed lookup" and isn't in the registry. nodes#new's four hardcoded radio buttons and nodes_controller's kind-specific case/when branches are both replaced by iterating/looking up this one registry - adding a new kind now means one new hash entry, not four scattered edits.
9 daysFix admin search results colliding with other search widgetserdgeist
layouts/admin.html.erb's top-bar search results div shared id "search_results" with nodes#new/nodes#edit's parent-search widget - since the layout renders on every admin page, whichever widget's JS ran last silently won the shared ID, putting top-bar results into the parent-search box on affected pages. Renamed to "menu_search_results" and updated admin_search.js to match. Also modernizes admin_search.js's event bindings from keyup/keydown/ keypress/paste/cut + .attr("value") to input + .val() throughout (menu_items, parent_search, move_to_search) - the multi-event binding was working around old IE compatibility that .val() + "input" already handles correctly. parent_search's result rendering now matches menu_search's convention (real <p>/<a> markup with a .result_path span) instead of a bare <a> in a throwaway wrapper div, so the same CSS rule now correctly applies to both. menu_search's JSON response gains node_path per result, matching what admin_search's own results already provide - not yet consumed by parent_search/move_to_search, which still render click-to-select links rather than navigable ones (correct for their purpose - selecting a value, not leaving the page). Known remaining gap, not fixed here: menu_items, parent_search, and move_to_search still all target the literal id "search_results" between themselves. No live collision today since none of the three currently share a page, but it's the same fragility as the bug above - tracked alongside the existing menu_items/parent_search/move_to_search consolidation backlog item rather than treated as resolved.
11 daysRetire event_information, superseded by nodes#show's event managererdgeist
The metadata-panel "Event" field on nodes#edit called event_information, a second, tag-less implementation of "list events + add one" - now redundant now that nodes#show has the tag-aware version with the auto-tag flash explanation. Confirmed via grep this was the only call site before removing both it and the method definition.
11 daysFix safe_return_to being uncallable from controllerserdgeist
Defined only in ApplicationHelper, which Rails auto-mixes into views but not controllers - so events#create and events#update, which call it directly, have been broken since introduction. Likely unexercised until now because every existing event was created via node.events.create! in the seed script, never through a real POST. Moved to ApplicationController as a protected method + helper_method declaration, so both controllers and views can call it (form_error_ messages stays in ApplicationHelper - it's genuinely view-only, content_tag isn't available in a controller either). Logic unchanged, caught by the new EventsController tests in the previous commit.
11 daysAdd self-service event creation from nodes#showerdgeist
nodes#show's events table now renders unconditionally (previously hidden entirely for a zero-event node) and gains an "add event" link. The suggested tag_list is derived from the page's own category tags via NodesHelper::DEFAULT_EVENT_TAG_BY_PAGE_TAG (erfa-detail/ chaostreff-detail -> open-day) rather than hardcoded, and degrades to a blank field for any node whose tags don't match - deliberately universal, not chapter-specific, since Updates have historically carried event dates the same way. events#new surfaces *why* the tag was pre-filled via flash.now (not flash - this is a same-request render, not a redirect), using an explicit auto_tag_source param passed alongside tag_list rather than having the controller re-derive the reason from node_id. No destroy link added to nodes#show's events list - deliberate, per existing subnav-semantics convention (destructive actions live on the resource's own views). Covered directly by test. Adds real coverage for EventsController, previously 100% commented-out scaffold, plus unit tests for the new tag-mapping helper and the weekday-abbreviation helpers from the prior commit.
11 daysAdd weekday abbreviation for open-today widgeterdgeist
RruleHumanizer gains WEEKDAY_NAMES_ABBR (Mo/Di/Mi/...) alongside the existing WEEKDAY_NAMES and WEEKDAY_NAMES_ADVERBIAL hashes, plus a self.wday_abbr(time, locale) utility mapping a Time's wday to its RFC5545 code and looking up the abbreviation - keeping RFC5545 vocabulary in one place rather than teaching ContentHelper about day codes directly. ContentHelper#weekday_abbr is a thin wrapper passing I18n.locale through. The partial now renders "Fr 19:00" instead of just "19:00", joined with a non-breaking space so the pair can't split across a line wrap. Takes an explicit Time rather than reading Date.today, matching this file's existing style of passing state in rather than reading it ambiently - and staying correct if the currently-unused calendar/_front_page_calendar partial (spanning six weeks, not just today) is ever revived and reuses this helper.