Runtime Decoupling Refactor Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Reduce coupling across runtime modules while keeping current endpoint and datetime behavior unchanged.

Architecture: Split runtime responsibilities into independent modules: endpoint parsing, translation state detection, locale decision, and time rendering. Keep language-router focused on routing/translation trigger, and make time-localizer consume a stable state interface instead of directly reading multiple side channels. Preserve behavior through test-first migration.

Tech Stack: Jekyll, vanilla JavaScript (IIFE modules), Node assert tests.


Task 1: Add behavior lock tests for current runtime contract

Files:

  • Create: tests/runtime-locale-decision.test.js
  • Modify: tests/endpoint-utils.test.js
  • Test: tests/runtime-locale-decision.test.js, tests/endpoint-utils.test.js, tests/time-localizer.test.js

Step 1: Write the failing test

// tests/runtime-locale-decision.test.js
const assert = require('assert');
const { resolveDisplayLocale } = require('../assets/js/endpoint-utils');

assert.strictEqual(resolveDisplayLocale('fr', null, 'fr', 'en-US', true), 'en-US');
assert.strictEqual(resolveDisplayLocale('fr', 'de', 'de', 'en-US', false), 'fr');
assert.strictEqual(resolveDisplayLocale('fr', 'de', 'de', 'en-US', true), 'de');

Step 2: Run test to verify it fails

Run: node tests/runtime-locale-decision.test.js
Expected: FAIL (new file not complete or missing cases)

Step 3: Write minimal implementation

Use current behavior as source of truth and fill complete assertions for:

  • no endpoint -> browser default
  • endpoint pending -> keep current
  • endpoint confirmed -> switch
  • clear endpoint after previous success -> browser default

Step 4: Run test to verify it passes

Run: node tests/runtime-locale-decision.test.js && node tests/endpoint-utils.test.js && node tests/time-localizer.test.js
Expected: PASS

Step 5: Commit

git add tests/runtime-locale-decision.test.js tests/endpoint-utils.test.js
git commit -m "test: lock runtime locale decision behavior"

Task 2: Extract translation status detection into a standalone module

Files:

  • Create: assets/js/translation-status.js
  • Modify: assets/js/time-localizer.js
  • Modify: _includes/head.html
  • Test: tests/translation-status.test.js

Step 1: Write the failing test

// tests/translation-status.test.js
const assert = require('assert');
const { isTranslationAppliedFromClassName } = require('../assets/js/translation-status');

assert.strictEqual(isTranslationAppliedFromClassName('translated-ltr'), true);
assert.strictEqual(isTranslationAppliedFromClassName('translated-rtl foo'), true);
assert.strictEqual(isTranslationAppliedFromClassName('foo bar'), false);

Step 2: Run test to verify it fails

Run: node tests/translation-status.test.js
Expected: FAIL with module not found

Step 3: Write minimal implementation

function isTranslationAppliedFromClassName(className) {
  var value = String(className || '');
  return /(^|\s)translated-(ltr|rtl)(\s|$)/.test(value);
}

Export for Node and attach for browser in IIFE style.

Step 4: Run test to verify it passes

Run: node tests/translation-status.test.js
Expected: PASS

Step 5: Commit

git add assets/js/translation-status.js assets/js/time-localizer.js _includes/head.html tests/translation-status.test.js
git commit -m "refactor: extract translation status detector"

Task 3: Extract datetime rendering into pure helper module

Files:

  • Create: assets/js/time-format.js
  • Modify: assets/js/time-localizer.js
  • Test: tests/time-format.test.js

Step 1: Write the failing test

// tests/time-format.test.js
const assert = require('assert');
const { formatDateTime } = require('../assets/js/time-format');

const iso = '2026-03-03T14:17:15.000Z';
const out = formatDateTime(iso, 'en-US', 'Asia/Shanghai');
assert.ok(typeof out === 'string' && out.length > 0);

Step 2: Run test to verify it fails

Run: node tests/time-format.test.js
Expected: FAIL with module not found

Step 3: Write minimal implementation

function formatDateTime(input, locale, timeZone) {
  var date = new Date(input);
  if (Number.isNaN(date.getTime())) return null;
  var options = timeZone ? { timeZone: timeZone } : undefined;
  return date.toLocaleString(locale || undefined, options);
}

Step 4: Run test to verify it passes

Run: node tests/time-format.test.js
Expected: PASS

Step 5: Commit

git add assets/js/time-format.js assets/js/time-localizer.js tests/time-format.test.js
git commit -m "refactor: isolate datetime formatting helper"

Task 4: Introduce runtime state channel and remove cross-module probing in localizer

Files:

  • Create: assets/js/runtime-state.js
  • Modify: assets/js/language-router.js
  • Modify: assets/js/time-localizer.js
  • Modify: _includes/head.html
  • Test: tests/runtime-state.test.js

Step 1: Write the failing test

// tests/runtime-state.test.js
const assert = require('assert');
const { createRuntimeState } = require('../assets/js/runtime-state');

const s = createRuntimeState();
s.setEndpoint('fr');
s.setTranslatedTarget('fr');
s.setTranslationApplied(true);
assert.deepStrictEqual(s.get(), {
  endpoint: 'fr',
  translatedTarget: 'fr',
  translationApplied: true
});

Step 2: Run test to verify it fails

Run: node tests/runtime-state.test.js
Expected: FAIL with module not found

Step 3: Write minimal implementation

Implement a tiny state container with:

  • get()
  • setEndpoint(value)
  • setTranslatedTarget(value)
  • setTranslationApplied(value)
  • optional subscribe(listener) for future decoupling

language-router writes endpoint and target updates to this channel.
time-localizer reads only this channel + browser locale/timezone.

Step 4: Run test to verify it passes

Run: node tests/runtime-state.test.js && node tests/endpoint-utils.test.js && node tests/time-localizer.test.js
Expected: PASS

Step 5: Commit

git add assets/js/runtime-state.js assets/js/language-router.js assets/js/time-localizer.js _includes/head.html tests/runtime-state.test.js
git commit -m "refactor: add runtime state channel for low coupling"

Task 5: Narrow MutationObserver scope and keep behavior

Files:

  • Modify: assets/js/time-localizer.js
  • Modify: assets/js/language-router.js
  • Test: tests/runtime-observer-scope.test.js

Step 1: Write the failing test

// tests/runtime-observer-scope.test.js
const assert = require('assert');
const { resolveObserverRootSelector } = require('../assets/js/time-localizer');
assert.strictEqual(resolveObserverRootSelector(), '.post-content, .page-content, .home');

Step 2: Run test to verify it fails

Run: node tests/runtime-observer-scope.test.js
Expected: FAIL (helper missing)

Step 3: Write minimal implementation

Expose and use one selector constant for observer roots:

  • .post-content
  • .page-content
  • .home

Fallback to document.body only when none exists.

Step 4: Run test to verify it passes

Run: node tests/runtime-observer-scope.test.js && node tests/endpoint-utils.test.js && node tests/time-localizer.test.js
Expected: PASS

Step 5: Commit

git add assets/js/time-localizer.js assets/js/language-router.js tests/runtime-observer-scope.test.js
git commit -m "refactor: narrow observer scope to content containers"

Task 6: Cleanup duplicated helpers and stale branches

Files:

  • Modify: assets/js/endpoint-utils.js
  • Modify: assets/js/language-router.js
  • Modify: assets/js/time-localizer.js
  • Test: tests/endpoint-utils.test.js, tests/time-localizer.test.js

Step 1: Write the failing test

Add assertions that old side-channel fallback branches are no longer needed once runtime state channel is present.

Step 2: Run test to verify it fails

Run: node tests/endpoint-utils.test.js && node tests/time-localizer.test.js
Expected: FAIL on deprecated paths

Step 3: Write minimal implementation

Remove duplicated resolution paths and keep single source for:

  • endpoint selection
  • translation applied status
  • display locale decision

Do not change external behavior.

Step 4: Run test to verify it passes

Run: node tests/endpoint-utils.test.js && node tests/time-localizer.test.js
Expected: PASS

Step 5: Commit

git add assets/js/endpoint-utils.js assets/js/language-router.js assets/js/time-localizer.js tests/endpoint-utils.test.js tests/time-localizer.test.js
git commit -m "refactor: remove duplicated runtime branches"

Task 7: Final verification and docs

Files:

  • Modify: docs/plans/2026-03-04-runtime-decoupling-refactor.md
  • Optional: README.md (if you choose to add runtime architecture notes)

Step 1: Run full verification

Run:

  • node tests/runtime-locale-decision.test.js
  • node tests/translation-status.test.js
  • node tests/time-format.test.js
  • node tests/runtime-state.test.js
  • node tests/runtime-observer-scope.test.js
  • node tests/endpoint-utils.test.js
  • node tests/time-localizer.test.js

Expected: all PASS

Step 2: Capture architecture summary

Document final module boundaries and data flow in this plan file under a Final Architecture section.

Step 3: Commit

git add docs/plans/2026-03-04-runtime-decoupling-refactor.md
git commit -m "docs: record runtime decoupling architecture and verification"