summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
2026-07-17Fix grammar for unknown action and discard_autosaveerdgeist
2026-07-17Retire recent changes widget, replace with action logerdgeist
2026-07-17Retire recent changes widget, replace with action logerdgeist
2026-07-17Give all text and password inputs border-radiierdgeist
2026-07-17Cutover to new Recent Changes widgeterdgeist
2026-07-16Construct DOM elements using escaping helpers to prevent XSSerdgeist
2026-07-16Add a script to backfill action log from existing recordserdgeist
2026-07-16Move rrule builder to a partial and share between events#new and events#editerdgeist
2026-07-16Aggregate selected weeks in a more human friendly formerdgeist
Weekdays appearing on a subset of the weeks of the month are now aggregated into a simpler form.
2026-07-16managed RRULE constructs now include week selectionerdgeist
Improve the humanizer to also understand weekly patters that an editor can manually select, on top of the single week rules. Extend template and javascript controller to reflect these changes.
2026-07-16Split current_unique_name in a setter and an accessorerdgeist
Also clean up some instance variable assignments on the way.
2026-07-16nodes#show's revisions subsection renamed to historyerdgeist
Now there's a separate revisions history and a full action log for a node. To reflect the extended scope, the section has been renamed to history.
2026-07-16Condense the action log into a scannable tableerdgeist
Several minor improvements to the action log presentation: * Now a table with date and human readable presentation as rows is displayed * If no changes in a title were detected, the old version is omitted * The "inferred" flag is demoted to the end of the line * You can zoom in on the node's history directly from a log line * byline for the first publish action is preserved * Revisions are directly linked to when a new one i published
2026-07-16Don't reference your own user as 'du' in action log. It breaks grammar.erdgeist
2026-07-16Add a reader for the action log at admin/logerdgeist
NodeActionsController#index lists entries newest-first, filterable by node_id or user_id -- the two zoom shapes the log was designed around. Rendering goes through NodeActionsHelper.action_summary, which builds one sentence per entry from metadata alone, so entries referencing deleted users or nodes render from their snapshots; live associations only upgrade names to links. Unknown verbs degrade to a generic sentence rather than an error, since the log outlives its vocabulary. The helper is the escaping boundary: every metadata value passes through h() before assembly. Actor names link to the log's own user zoom rather than the unused users page -- inspecting a suspicious user's other actions is the intended workflow. Publish entries with a translation_diff expose a collapsed per-locale change table linking out to the revision itself. Sentences live in en.yml/de.yml following the existing widget-string convention. nodes#show links to its node's zoomed log.
2026-07-16Record the full lifecycle contract in NodeAction entrieserdgeist
A contract comment above NodeAction.record! now specifies every verb's metadata shape. NodeAction.head_diff computes the publish diff between an outgoing head and its replacement -- default-locale title pair always, author/tags pairs and template/assets/abstract/ body flags only when changed, and a per-locale translation_diff with added/removed/changed status. It is a pure function of its two pages, shared by publish, rollback, and the future backfill, and reads translation rows directly so fallbacks never masquerade as content. publish entries carry via ("draft" or "revision"); restore_revision! is now transactional, takes the acting user, and logs through the same diff. Staged slug/parent changes applied at publish log a move entry with the path pair. Node creation logs a create entry with initial title and path. The draft-scoped translation_destroy writer is retired -- locale removal is recorded by the publish diff, where it becomes public fact.
2026-07-16Provide parent_unique_name in nodes#new if parent is already pre-seterdgeist
2026-07-16Remove some remnants of legacy admin wizard interfaceerdgeist
2026-07-15Add NodeAction: an append-only log of who did what to a nodeerdgeist
node_id/page_id/user_id are lookup and ordering only -- all three nullify on delete, so an entry outlives its actor and its subject. Everything that must survive those deletions lives in a mandatory metadata jsonb written once at creation: the actor's username, the node's human-readable name (pinned to the default locale), and action-specific extras such as publish's title from/to. NodeAction.record! is the single constructor, so every entry gets the same baseline metadata without each call site re-implementing it. occurred_at is one field for live and backfilled entries alike; inferred_from distinguishes them -- nil means witnessed at the moment it happened, populated names how a backfilled entry was estimated. Instrumented so far: publish (crediting the actual publisher, threaded through from the controller -- previously nobody had the act of publishing recorded anywhere), revert's discard_autosave and destroy_draft branches, and translation destroy. publish_draft! now runs in a transaction so the promotion and its log entry land together. The remaining verbs follow once this mechanism has proven itself.
2026-07-15Make the recent-changes page match the widget it links fromerdgeist
nodes/recent rendered the generic node-listing partial (id, actions, current lock owner, revision number) -- nothing resembling what 'See all recent changes' actually promised to show more of. Both surfaces now render the same extracted partial, so they can't tell two different stories again. Also: head.editor correctly identifies who last wrote the content sitting in head, but publish_draft! never touches editor at all -- nobody has ever had the specific act of publishing recorded anywhere. 'Published by X' was a claim the data never supported, only incidentally true when the same person who drafted something also happened to publish it. Relabeled to 'last edited by' -- same data, accurately described. A real publish-actor is separate, future work, once the action log exists. Basic list styling added for the full page, which had never rendered outside the widget's own compact five-line context before.
2026-07-15Remove dead admin/_recent_changes partialerdgeist
2026-07-14Add scaffolding for a proper tech documentation from everything I could remembererdgeist
2026-07-14Supply related-asset URLs/name and public inline-image CSSerdgeist
RelatedAssetsController#create's JSON includes large_url, original_url, and name alongside thumb_url/url; the related-asset list items in both editors carry them as data-large-url, data-original-url, and data-name. ccc.css gets the public-facing .inline-image--full/--half/--left/ --right rules -- admin.css and TinyMCE's content_style cover the cockpit and in-editor renderings respectively.
2026-07-14Add inline image insertion for page bodies, plus a headline-conflict badgeerdgeist
Custom TinyMCE toolbar button opens a picker scoped to a page's own attached images. Selecting one and a placement (full width, left, right) inserts <a class="glightbox" data-gallery="page-N"> wrapping an <img>, sharing the existing headline-image lightbox gallery. No native TinyMCE image dialog or resize handles -- placement is fixed to the three options via CSS classes only. extended_valid_elements defines a and img explicitly and symmetrically; valid_classes allows the placement classes and glightbox specifically. content_style renders placement inside the editing iframe, since ccc.css doesn't load there. A pre-save pass removes any a.glightbox left without an img, covering in-editor deletion regardless of how it happened. Alt text comes from the asset's name, escaped. A badge marks whichever attached image sits at position 0, since _headline_image.html.erb always renders that position as the page's automatic headline. Shown in the sidebar Images list and in the picker grid.
2026-07-14Extend static-asset cache-busting to the public layouterdgeist
mtime_busted_path only ever covered the four admin-only static files. The public layout has the identical exposure -- ccc.css, glightbox's vendored CSS/JS, and public.js are all served straight from public/ with no pipeline fingerprinting either -- just never got the same treatment.
2026-07-14Crop headline images to aspect ratio instead of distorting themerdgeist
STYLES restructured from a single geometry: string per style to a full args: array, since this fix needs -gravity and -extent alongside -resize, which one geometry string can't express.
2026-07-14Add a :large FileAttachment style for full-width inline body imageserdgeist
Sized generously (1600x1600>) rather than against today's cramped 490px content column specifically -- shrinking an oversized source to fit a narrow container is free and looks fine; a source too small for a future, wider redesign has no fix short of re-uploading every image. Purely additive: nothing reads :large yet, and generate_variants already loops over the whole STYLES hash generically, so no other code needed to change for new uploads to start getting it. Test suite's own hardcoded style lists updated in the same commit so they don't quietly drift out of sync with what the app actually defines.
2026-07-14Retire wipe_draft! and find_or_create_draft from production codeerdgeist
Both had already lost their reason to exist as production API: wipe_draft!'s one remaining callsite (nodes#show) was removed two sessions ago, and find_or_create_draft had zero production callers left at all -- confirmed by a fresh grep, not assumed -- every one of its ~65 call sites was test setup, unrelated to what those tests actually cover. wipe_draft! is deleted outright, along with its two tests -- the lock/draft/autosave cleanup it silently performed already has explicit, always-visible manual equivalents (Unlock, Discard Autosave, Destroy Draft), so nothing real is lost. find_or_create_draft moves to test_helper.rb as a plain method on ActiveSupport::TestCase, alongside the create_node_with_draft/ create_node_with_published_page helpers already living there -- extending the framework's own designated test-extension point rather than reopening the Node model from test code. Its three tests of real dispatch behavior (idempotency on repeat calls, and raising when a second user contends for the lock) are kept, since ~65 other tests depend on this helper actually working correctly; only the call syntax changed, from node.find_or_create_draft(user) to find_or_create_draft(node, user).
2026-07-14Add chapter images: thumbnail on the list, headline image on the detail pageerdgeist
_chapter.html.erb (the aggregator partial used by the erfas/ chaostreffs overview pages) never rendered an image at all -- editors attaching a hackerspace photo via the asset picker would see zero visible effect on exactly the pages they'd check first. chapter_detail.html.erb (the individual page template) had the same gap. Both fixed by reusing existing infrastructure rather than inventing anything: the overview list gets a small, aspect-preserving :thumb image beside the heading; the detail page gets the app's existing headline_image helper, the same one every other full-page template already uses.
2026-07-14Add a toggleable live preview panel to both editorserdgeist
Hidden by default, shares horizontal space with the body editor once opened rather than a separate section, and only requests the iframe's src on first toggle -- no preview traffic at all until someone actually wants to see it. Refreshes automatically on every successful autosave; a separate force-render button re-attempts a save-and- refresh cycle immediately, for when the normal 7-second cycle has been interrupted (a lock-lost error, a transient failure) and waiting for the next tick isn't good enough.
2026-07-14Make revisions locale awareerdgeist
2026-07-14Add tooltips to all navbar itemserdgeist
2026-07-14Make tags a pill list on nodes#showerdgeist
2026-07-14Store return_to before resetting session in loging. Fixes returning to an admin page before logging inerdgeist
2026-07-14Re-add language selector, but fix the mis-nomer for english pageserdgeist
2026-07-13Merge branch 'erdgeist-revive-events'erdgeist
2026-07-13Remove orphaned language selector for admin viewserdgeist
2026-07-13Add cache-buster using mtime of css and js files outside the asset pipelineerdgeist
2026-07-13Remove redundant default translation from the translations subsectionerdgeist
2026-07-13Fix: p elements can't contain buttons. Convert it to a diverdgeist
2026-07-13Make revision diffs locale-aware, defaulting to whichever locale changederdgeist
diff_against compared title/abstract/body under whatever I18n.locale happened to be ambient, with no concept of 'diff this translation specifically' -- so a change confined to one locale was invisible to Diff Head vs. Draft regardless of which locale you were looking at when you clicked it, exactly the 'yields nothing' complaint from earlier this session. Page#diff_against gains a locale: keyword, additive only -- nil preserves the exact original ambient-locale behavior every existing caller and test already depends on; passing a locale switches to reading each side's actual PageTranslation row directly, same fallback-free reasoning as Page#translation_summary. Page#locale_diff_summary reports one entry per locale present on either side, so an added or removed translation counts as a change even where content matches everywhere it exists on both. RevisionsController#diff now resolves a real locale before diffing -- defaulting to whichever locale actually changed, falling back to the default locale only when nothing did -- and the view carries that locale through every existing control (view toggle, layer-pair buttons, the revision-select form) so it and the view/layer-pair axis stay independently selectable rather than resetting each other.
2026-07-13clone_attributes_from: update translations in place, not delete+recreateerdgeist
Every promotion (autosave creation, draft creation, draft save) ran every locale's translation through delete-all-then-recreate unconditionally, giving every locale a fresh created_at/updated_at regardless of whether its content actually changed. Confirmed via Node.find(546).draft.translations -- both DE and EN shared one identical timestamp despite only EN having been edited. This silently defeated Page.find_with_outdated_translations' whole staleness comparison: every save reset every locale back into lockstep, so timestamps could never actually drift apart the way the feature depends on. The existing test for it only ever passed by manually backdating a timestamp with record_timestamps = false, bypassing normal save flow entirely -- not something that happens through ordinary editing. Now updates a matching locale's translation only when its own attributes actually differ, creates one genuinely new, and removes one genuinely gone from the source -- same end state, but real per-locale timestamps survive an unrelated save. search_vector is excluded from both the comparison and the copied attributes: it's DB-trigger-maintained from title/abstract, not real content, and comparing a precomputed tsvector risked a false 'changed' from representation noise alone. Also reloads the source's translations association, not just self's -- clone_attributes_from previously only guaranteed a fresh self, leaving any caller holding a page reference across an earlier mutation vulnerable to reading a stale cached association.
2026-07-13Fix autosave bug: sync tinymce's state back to textarea before serializingerdgeist
2026-07-13Fix shared preview redirecting to admin for an ordinary new drafterdgeist
superseded/currently_public used published_at presence as a proxy for page-row relevance, but published_at is carried forward onto every new draft descended from previously-published content (Page#clone_attributes_from, Node#save_draft!) -- so it read true for any draft on an already-published node, whether or not that specific draft had ever gone live itself. Compare draft_id/head_id identity directly instead; no timestamp involved. A stale (superseded) link now redirects to the live public page rather than an admin URL an anonymous holder can't reach anyway.
2026-07-13Wire real autosave into translation editingerdgeist
The shared TinyMCE setup initializes cccms.setup_autosave() on any page with a textarea.with_editor, unconditionally starting a 7-second interval that submits to the form's data-autosave-url -- which the translation edit form never set, so the interval PUT the current page URL itself and 404'd on a nonexistent route. Fixed by actually giving it something to talk to, rather than suppressing it: a real autosave endpoint, and update now goes through Node#autosave!/#save_draft! -- the same pipeline the primary editor uses, fixed earlier this session for exactly this kind of cross-locale carryover. This was the autosave-buffer parity already flagged as due after the proof of concept; the shared JS just forced the timing.
2026-07-13Add per-node translation management: list, edit, destroy, compareerdgeist
Replaces the old locale-switch-and-edit-the-same-screen workflow, which conflated presentation locale with content locale and let an editor silently drift into editing the wrong language with no persistent signal that anything had changed. Non-default-locale content now has its own explicit routes and screens, never sharing a route param with the ambient chrome locale. - PageTranslationsController: index/show/edit/update/destroy, scoped to Page.non_default_locales; update handles first-time creation too, so there's no separate new/create step. - Reads go through the actual PageTranslation row (Page#translation_summary), never through the Globalize fallback-bearing accessor -- fallback is correct for public rendering but wrong for editing, where a missing translation needs to look empty, not borrowed from another locale. - Translations ride on the page's own draft/head cycle; no independent publish state. - nodes#show gains a Translations section (per-locale Lock+Edit / Create+Lock, Destroy, a link into the read-only Compare view) and a locale indicator on its own default-locale content; nodes#edit, nodes#update, and nodes#autosave are pinned to the default locale via Globalize.with_locale regardless of the ambient route locale. - nodes#show no longer double-loads the node or calls wipe_draft! on every view (see previous commit for why that's now safe). - .preview_link_row is renamed .aligned_action_row now that it has a second real consumer.
2026-07-13wipe_draft!: add a grace period to the lock-with-no-draft brancherdgeist
Its other two branches both require a day's inactivity before touching anything; this one didn't, so a lock acquired seconds ago with nothing drafted yet looked identical to one abandoned for a week -- confirmed with a throwaway script showing a lock cleared inside a single method call. Guarded on autosave.nil? specifically, since the method's first line already guarantees any autosave reaching this branch is a day old by construction.
2026-07-13nodes#show: show each revision's editor, not its byline authorerdgeist
page.user is the piece's credited author, carried forward across revisions; page.editor is whoever actually produced this specific revision. The revisions list wants the latter -- found by inspection while testing something unrelated.
2026-07-13Set config.i18n.available_locales explicitlyerdgeist
Rails-i18n's bundled locale data means an unset available_locales defaults to every locale it ships pluralization/date rules for -- several hundred, not the two this app actually supports. Harmless everywhere the outer route scope's own /de|en/ regex already narrowed whatever reached I18n.available_locales.include?(...), but the first code to read the list directly (Page.non_default_locales) took it at face value. Also closes a related, independently-silent gap in Page#rewrite_links_in_body, which used the same unconstrained list to decide whether a link was already locale-prefixed.
2026-07-13Carry over other-locale translations when creating an autosaveerdgeist
Node#autosave! only copied assets forward onto a newly created autosave, not translations. Invisible for single-locale nodes, but a node with an existing multi-locale head/draft that gets locked and edited in one locale would silently drop every other locale's translation from the new autosave -- and from there into the draft and eventually head, since save_draft! faithfully clones whatever the autosave actually holds. Fixed by using clone_attributes_from, which already does a complete clone (translations, assets, tags, template, published_at) -- the same method save_draft! already relies on two branches later, so no new mechanism, just closing the one place still doing a partial, hand-rolled version of it.