<feed xmlns='http://www.w3.org/2005/Atom'>
<title>cccms/test/controllers, branch erdgeist-modernize-auth</title>
<subtitle>[no description]</subtitle>
<id>https://erdgeist.org/gitweb/cccms/atom?h=erdgeist-modernize-auth</id>
<link rel='self' href='https://erdgeist.org/gitweb/cccms/atom?h=erdgeist-modernize-auth'/>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/'/>
<updated>2026-07-16T01:30:01Z</updated>
<entry>
<title>Add a reader for the action log at admin/log</title>
<updated>2026-07-16T01:30:01Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-16T01:30:01Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=190daaa8a0bb79e750894b83e31b9d99f7d900a4'/>
<id>urn:sha1:190daaa8a0bb79e750894b83e31b9d99f7d900a4</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Record the full lifecycle contract in NodeAction entries</title>
<updated>2026-07-16T00:40:03Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-16T00:40:03Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=c0c9250141c2fa64fed967b8b9ad9bada3694c3d'/>
<id>urn:sha1:c0c9250141c2fa64fed967b8b9ad9bada3694c3d</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Add NodeAction: an append-only log of who did what to a node</title>
<updated>2026-07-15T02:23:09Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-14T23:41:34Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=d56155231814633e04f856d22646fea24ef97011'/>
<id>urn:sha1:d56155231814633e04f856d22646fea24ef97011</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Add a :large FileAttachment style for full-width inline body images</title>
<updated>2026-07-14T13:10:33Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-14T13:10:33Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=810cddcb122462d62b31b03295e001ea5f0252ba'/>
<id>urn:sha1:810cddcb122462d62b31b03295e001ea5f0252ba</id>
<content type='text'>
Sized generously (1600x1600&gt;) 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.
</content>
</entry>
<entry>
<title>Retire wipe_draft! and find_or_create_draft from production code</title>
<updated>2026-07-14T12:31:18Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-14T12:31:18Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=cca0e57b21d506cd341b927984bcb68f0e461783'/>
<id>urn:sha1:cca0e57b21d506cd341b927984bcb68f0e461783</id>
<content type='text'>
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).
</content>
</entry>
<entry>
<title>Make revisions locale aware</title>
<updated>2026-07-14T00:17:04Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-14T00:17:04Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=88ebeaac7fcf140cbab489e841bf9c197f84c4ea'/>
<id>urn:sha1:88ebeaac7fcf140cbab489e841bf9c197f84c4ea</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Make revision diffs locale-aware, defaulting to whichever locale changed</title>
<updated>2026-07-13T17:12:56Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-13T17:12:56Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=ba789678f9e8ed189413afb1446ad210df34e488'/>
<id>urn:sha1:ba789678f9e8ed189413afb1446ad210df34e488</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Fix shared preview redirecting to admin for an ordinary new draft</title>
<updated>2026-07-13T15:09:45Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-13T15:09:45Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=40adeec1bcc6fa276d765e3b9c37e781c64a48c1'/>
<id>urn:sha1:40adeec1bcc6fa276d765e3b9c37e781c64a48c1</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Wire real autosave into translation editing</title>
<updated>2026-07-13T15:09:31Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-13T15:09:31Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=aa14587b052a0aa7884aaf27183981975168ab14'/>
<id>urn:sha1:aa14587b052a0aa7884aaf27183981975168ab14</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Add per-node translation management: list, edit, destroy, compare</title>
<updated>2026-07-13T14:36:38Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-13T14:36:38Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=0b6ae26f3b4c3c8278e569fa6644a544e996c25d'/>
<id>urn:sha1:0b6ae26f3b4c3c8278e569fa6644a544e996c25d</id>
<content type='text'>
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.
</content>
</entry>
</feed>
