Skip to content

♻️ Rules.Content - Script a one-off reformat of all rule.mdx files to the TinaCMS standard #2683

Description

@joshbermanssw

❗❗ DO NOT ACTION YET ❗❗

      > 3. Do you think all SSW rules should be updated to the new format
Yes - When speaking to Jack P and Kosta, they agreed there should be a script that goes through and formats all rules to use TinaCMS standard, so that we are less likely to run into this problem.

Adam disagrees with doing this because he’s on the fence.

Wicksy and I disagree too; he would prefer to pay the price for this formatting one PR at a time.

      > 4. Do you think that TinaCMS should have an option “[x] Don’t clean up markdown (for simpler reviewing)
❌ No - TinaCMS runs a formatter on all markdown that is parsed through it. This is to enforce certain rules and reduce the likelihood of errors in implementation. Without these guardrails - we’d be more likely to have issues arising from drifting standards. 

Hi Team!

cc: @KostaMadorsky, @0xharkirat, @Marxoz, @PothieuG, @Freego1783, @adamcogan

Related to #2375 - this PBI is the concrete implementation of that idea: write a script, run it once across all of SSW.Rules.Content, and get every rule.mdx onto the TinaCMS serializer's output format.

Pain

Most of the ~3,800 rules in SSW.Rules.Content are still in the legacy (V2) Markdown format. The moment anyone edits one of them through TinaCMS, Tina re-serializes the whole file on save, so a 1-line content change comes back as a full-file rewrite.

Real example - SSW.Rules.Content#11881 added a single list item ("Number 12: Fast Forward Edit") and renumbered the items after it. The diff:

public/uploads/rules/video-editing-terms/rule.mdx   +373  -456

One paragraph of intent, buried in 800+ lines of noise. Reviewers cannot see what actually changed, so PRs either get rubber-stamped or stall.

This will keep happening on every single rule until each one has been through Tina once. Doing it as a byproduct of normal editing means we carry the pain for years. Doing it once, deliberately, in a dedicated PR costs us one review we can safely skim.

What "the TinaCMS standard" actually means

These are the transformations observed in PR #11881, comparing legacy input to Tina's saved output:

Frontmatter

  1. Keys are reordered from alphabetical into the schema order defined in tina/collection/rule.tsx: type, title, uri, categories, authors, related, redirects, guid, seoDescription, then the audit fields.
  2. Sequence items get indented 2 spaces under their key (- title: becomes - title:).
  3. URLs and strings containing special characters get single-quoted ('https://ssw.com.au/people/andrew-forsyth', 'Piers Sinclair [SSW]').
  4. Dates convert to ISO 8601 with milliseconds: 2023-01-19 07:01:07+00:00 becomes 2023-01-19T07:01:07.000Z.
  5. Long values stop being wrapped at ~80 chars and go onto a single line (seoDescription is the usual culprit).

Body

  1. JSX expression props collapse to plain string props: description={"Video: ..."} becomes description="Video: ...".
  2. Runs of 2+ blank lines around MDX components (<youtubeEmbed />, <endIntro />, <boxEmbed />) collapse to a single blank line.
  3. Blank lines inside body={<>...</>} children are emitted with the surrounding indentation as trailing whitespace.

Point 8 matters: if our script emits clean blank lines there but Tina emits indented ones, every rule drifts again on its next save and we are back where we started.

Suggested approach

Do not reimplement the serializer. Reuse Tina's own, so the output matches byte-for-byte:

  1. Load the rule collection schema from tina/collection/rule.tsx.
  2. For each rule.mdx, parse with parseMDX and re-emit with stringifyMDX from @tinacms/mdx (the same path the Tina backend takes on save), pinned to the versions this repo uses today (tinacms 3.10.0, @tinacms/cli 2.5.3).
  3. Write the file back only if the bytes changed.
  4. Verify by round-tripping: running the script a second time must produce zero changes.

The script should live in SSW.Rules (where the schema lives) and be runnable against a local clone of SSW.Rules.Content.

Acceptance Criteria

  1. A repeatable script exists that reformats rule.mdx files to the TinaCMS serializer's exact output.
  2. The script is idempotent - a second run over already-formatted content produces zero changes.
  3. All rule.mdx files in SSW.Rules.Content are reformatted in a single dedicated PR containing no content changes.
  4. lastUpdated, lastUpdatedBy, and lastUpdatedByEmail are left untouched. A formatting pass must not rewrite every rule's audit trail or flood "Recently updated rules" with bot edits.
  5. No rendered output changes. Spot-check a sample of reformatted rules on a preview deployment against production, including rules with boxEmbed, youtubeEmbed, tables, and code blocks.
  6. Rule count, guid values, uri values, and redirects are all unchanged after the run (diff the frontmatter values, not the formatting).
  7. After the pass, editing a rule in TinaCMS and saving produces a diff limited to the actual change.
  8. The formatting commit SHA is added to .git-blame-ignore-revs in SSW.Rules.Content so git blame still points at the real authors.

Notes and risks

  • Timing. This invalidates every open PR and in-flight Tina branch in SSW.Rules.Content. Run it when open PRs are at a low, and give the team notice to merge or rebase first.
  • Verification burden. Nobody can meaningfully review a 3,800-file diff line by line. Confidence has to come from the round-trip test (AC2), the frontmatter value diff (AC6), and rendered spot-checks (AC5), not from reading the PR.
  • Follow-up. ♻️ Normalize formatting across Rules V3 to reduce noisy pull requests #2375 also asks for CI enforcement to stop future drift. Worth its own PBI once the one-off pass has landed, since drift should be near-zero afterwards.

Suggested tasks

  • Confirm the exact serializer entry point Tina's backend uses on save
  • Write the script in SSW.Rules and test it on ~20 representative rules
  • Verify idempotency and frontmatter value preservation across the full set
  • Deploy a preview and spot-check rendered output
  • Announce the freeze window to the team
  • Run the full pass and raise the dedicated PR
  • Add the commit SHA to .git-blame-ignore-revs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions