summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
11 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.
11 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.
11 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.
12 daysAdd local only test Makefile to .gitignoreerdgeist
12 daysAdd a action bar to the node edit view. This is the first application of a proposed new layouterdgeist
12 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.
12 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.
12 daysRoute Save and autosave through the new Node methods (addendum)erdgeist
12 daysFix node_content count in show-with-published-draft testerdgeist
Fallout from the earlier nodes#show heading change, which replaced the Title row with an <h1> -- unrelated to the autosave work in the preceding commits.
12 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.
12 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.
12 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.
12 daysWhitespace fixeserdgeist
12 daysWIP: dynamic rrule builder, javascript sideerdgeist
12 daysLink new metadata behaviour and layout in css and javascripterdgeist
12 daysWhitespace fixeserdgeist
12 daysPromote page title to headline in nodes#showerdgeist
12 daysTuck away metadata section in nodes#edit behind a summary/detail cascadeerdgeist
12 daysWIP: dynamic rrule buildererdgeist
12 daysMake sure that page title actually is saved through to ORMerdgeist
13 daysFix subheadings in admin wizard viewerdgeist
13 daysFix column count between table header and bodyerdgeist
13 daysAdd or fix class names for the link underline rules to matcherdgeist
13 daysAdd class destructive to destructive buttonserdgeist
13 daysAdopt button pills in viewserdgeist
13 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
13 daysAdd link and lists to tinymce, wash pasted blocks clean of classeserdgeist
13 daysRemove stale public_bundle.js reference from asset manifesterdgeist
13 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).
13 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.
14 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.
14 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.
14 daysFix test to match the current layouterdgeist
14 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.
14 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.
2026-07-06Add 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.
2026-07-06Add 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.
2026-07-06Drop dead custom_rrule column from eventserdgeist
Forms were already cleaned up in an earlier commit; the column itself was the last remnant. Dropping the column surfaced a live reference in events#index (fixed separately, 970f108) - this migration file itself just never got staged until now.
2026-07-06Add FK with ON DELETE CASCADE on occurrences.event_iderdgeist
Was the last remaining gap in the occurrence-orphaning fix from earlier this session: dependent: :destroy on Event#occurrences covers deletion through ActiveRecord, this covers anything that bypasses it (raw SQL, Model.delete). Confirmed zero existing orphans and zero NULL event_ids before adding the constraint (event_id stays nullable - an occurrence without an event is valid, one with a bogus event_id is not). Also adds the index that should have existed on this column already - Postgres needs one for the cascade to be anything other than a full table scan per delete, and none existed before this. deferrable: :immediate added so legitimate application code can still defer the check within a transaction if ever needed - unrelated to, and not a fix for, the fixture-loading issue below. Applying this constraint broke fixture loading entirely (config fix: 81a07bf) and exposed a live bug in events#index (fix: 970f108), both already committed separately - this migration file itself just never got staged until now.
2026-07-06Strengthen event/occurrence cascade test with real assertionserdgeist
test_can_remove_a_node_with_an_event previously just called node.destroy and get :index with no assertions at all - would pass whether or not occurrences were actually cleaned up, or even if index rendered correctly afterward. Now confirms occurrences genuinely exist before destroy (otherwise a passing post-destroy count of zero is meaningless - indistinguishable from "nothing to cascade in the first place"), scopes the count to this event specifically rather than a global Occurrence.count that could coincidentally pass regardless of whether this cascade works, and checks the trailing index request actually succeeds rather than just not raising. First real test of the occurrences.event_id FK constraint added earlier this session, not just the application-level dependent: :destroy.
2026-07-06Remove 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.
2026-07-06Disable verify_foreign_keys_for_fixtures for test enverdgeist
Adding the occurrences.event_id FK constraint broke fixture loading entirely (every test failing at setup) with a misleading "Foreign key violations found in your fixture data" error. The real cause: this Rails feature does its own proactive integrity check during fixture insertion, independent of the constraint's own deferrability, by directly flipping pg_constraint.convalidated and re-validating - which needs catalog-level privilege the app's DB role correctly doesn't have. No actual fixture data was ever invalid; the check itself couldn't run. Scoped to test.rb only - this never affected real application behavior, and Postgres still fully enforces the actual constraint everywhere, in every environment, regardless of this setting.
2026-07-06Add migration for the default template name featureerdgeist
2026-07-06Add 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.
2026-07-05Whitespace fixeserdgeist
2026-07-05Surface 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.
2026-07-05Add 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.
2026-07-05Fix 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.
2026-07-04Retire 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.
2026-07-04Fix 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.