<feed xmlns='http://www.w3.org/2005/Atom'>
<title>cccms/app/controllers/nodes_controller.rb, 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-16T13:12:07Z</updated>
<entry>
<title>Split current_unique_name in a setter and an accessor</title>
<updated>2026-07-16T13:12:07Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-16T13:12:07Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=b701669d0d9a5d7be01f99b8e725c05c6c5d52ce'/>
<id>urn:sha1:b701669d0d9a5d7be01f99b8e725c05c6c5d52ce</id>
<content type='text'>
Also clean up some instance variable assignments on the way.
</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>Provide parent_unique_name in nodes#new if parent is already pre-set</title>
<updated>2026-07-15T23:40:37Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-15T23:40:37Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=fb7b8233f1bbf7acb88d48f96e6f81cefe168d1c'/>
<id>urn:sha1:fb7b8233f1bbf7acb88d48f96e6f81cefe168d1c</id>
<content type='text'>
</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>Remove redundant default translation from the translations subsection</title>
<updated>2026-07-13T17:28:49Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-13T17:28:49Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=0113d895e2fd8bf2cf88ae0196b9ab144ac9350b'/>
<id>urn:sha1:0113d895e2fd8bf2cf88ae0196b9ab144ac9350b</id>
<content type='text'>
</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>
<entry>
<title>Extract drafts/recent query logic from controller into Node</title>
<updated>2026-07-12T12:26:30Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-12T12:26:30Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=5803c59192b7fb05840d0b452eb64d9f997f3d8f'/>
<id>urn:sha1:5803c59192b7fb05840d0b452eb64d9f997f3d8f</id>
<content type='text'>
Node.drafts_and_autosaves and Node.recently_changed replace inline
query logic in NodesController#drafts/#recent -- pure refactor, no
behavior change for either action. The real reason: the dashboard's
upcoming abridged widgets need the exact same queries the full pages
already use, just limited and (for drafts) sorted with the current
user's own locked nodes first. Better to share one method than let a
widget and a full page quietly drift onto two versions of "what counts
as a current draft."

current_user_id: is an explicit, optional argument rather than a
separate method -- ordering by lock ownership only applies when a user
is given; omitted entirely, it's pure recency, exactly today's
behavior. Needed Arel.sql wrapping a sanitized CASE expression before
.order would accept it -- sanitize_sql_array only vouches for the
values inside the string, a separate Rails safety check still refuses
any string shaped like more than a plain column reference unless it's
explicitly marked as already-vetted.
</content>
</entry>
<entry>
<title>Give the sitemap its own view, with collapse and descendant counts</title>
<updated>2026-07-12T00:15:44Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-12T00:15:44Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=45bf65d04d046c0ea4a1150096b2a9b846d6c0b8'/>
<id>urn:sha1:45bf65d04d046c0ea4a1150096b2a9b846d6c0b8</id>
<content type='text'>
Extracted from admin#index's inline table into NodesController#sitemap.
Nested &lt;details&gt;/&lt;summary&gt; per branch, one linear pass over the
existing flat [node, level] list (no added queries) -- each node's own
descendant count computed the same way, via a small stack rather than
re-walking the tree per node. Branches under updates/, club/erfas,
club/chaostreffs, and disclosure start collapsed by default
(CccConventions::SITEMAP_COLLAPSED_PATHS); any branch currently
collapsed, whether by that default or because someone just closed it,
is highlighted via a plain :not([open]) selector -- no state tracked
outside the DOM itself.

Dropped the update?-post exclusion this view used to rely on -- no
longer needed now that updates/ collapses instead of being filtered
out, so its real children (previously silently absent) now show up
correctly. admin#index's own, separate @sitemap query is unchanged;
that view has no collapse mechanism to compensate and wasn't part of
this.
</content>
</entry>
<entry>
<title>Add drafts/recent/mine/chapters admin node views</title>
<updated>2026-07-11T21:43:02Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-11T21:43:02Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=92c394b43a0603743b914c6298aab986805ca990'/>
<id>urn:sha1:92c394b43a0603743b914c6298aab986805ca990</id>
<content type='text'>
Four NodesController actions -- drafts, recent, mine, chapters --
each building its own base scope, sharing one private method
(index_matching) for search narrowing and pagination. Wizard rewrite
to link into these instead of rendering its own tables is a separate,
later step.

Node.editor_search backs the shared "q" narrowing: an ILIKE substring
match against title/abstract on whichever of head or draft is
present, splitting the term on whitespace and requiring every word to
match somewhere independently, not as one phrase, since real words can
end up separated by markup in the underlying HTML. Deliberately
separate from Node.search, the public content search, which stays
tsvector-based and head-only.

chapters generalizes into /admin/nodes/tags/:tags for an arbitrary
tag list (OR'd, not AND'd), sharing the controller action but
rendering its own template rather than branching inside one view.
</content>
</entry>
<entry>
<title>Destroying Draft or Discarding Autosave drops you where you left</title>
<updated>2026-07-10T00:13:02Z</updated>
<author>
<name>erdgeist</name>
<email>erdgeist@erdgeist.org</email>
</author>
<published>2026-07-10T00:13:02Z</published>
<link rel='alternate' type='text/html' href='https://erdgeist.org/gitweb/cccms/commit/?id=c2b2648d327e1c1749c37fe2e58cd051ed871547'/>
<id>urn:sha1:c2b2648d327e1c1749c37fe2e58cd051ed871547</id>
<content type='text'>
</content>
</entry>
</feed>
