« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/content-function.js2
-rw-r--r--src/content/dependencies/generateAlbumInfoPage.js5
-rw-r--r--src/content/dependencies/generateArtistGroupContributionsInfo.js7
-rw-r--r--src/content/dependencies/generateTrackInfoPage.js10
-rw-r--r--src/content/dependencies/index.js10
-rw-r--r--src/content/dependencies/listAlbumsByDuration.js8
-rw-r--r--src/content/dependencies/listAlbumsByTracks.js7
-rw-r--r--src/content/dependencies/listArtistsByCommentaryEntries.js6
-rw-r--r--src/content/dependencies/listArtistsByContributions.js7
-rw-r--r--src/content/dependencies/listArtistsByDuration.js8
-rw-r--r--src/content/dependencies/listArtistsByName.js6
-rw-r--r--src/content/dependencies/listGroupsByAlbums.js6
-rw-r--r--src/content/dependencies/listGroupsByDuration.js8
-rw-r--r--src/content/dependencies/listGroupsByLatestAlbum.js8
-rw-r--r--src/content/dependencies/listGroupsByTracks.js7
-rw-r--r--src/content/dependencies/listTagsByUses.js7
-rw-r--r--src/content/dependencies/listTracksByDate.js6
-rw-r--r--src/content/dependencies/listTracksByDurationInAlbum.js8
-rw-r--r--src/content/dependencies/listTracksByTimesReferenced.js7
-rw-r--r--src/content/dependencies/transformContent.js6
-rw-r--r--src/content/util/groupTracksByGroup.js2
-rw-r--r--src/data/language.js5
-rw-r--r--src/data/things/album.js6
-rw-r--r--src/data/things/art-tag.js6
-rw-r--r--src/data/things/artist.js4
-rw-r--r--src/data/things/cacheable-object.js4
-rw-r--r--src/data/things/flash.js4
-rw-r--r--src/data/things/group.js4
-rw-r--r--src/data/things/homepage-layout.js4
-rw-r--r--src/data/things/index.js15
-rw-r--r--src/data/things/thing.js16
-rw-r--r--src/data/things/track.js20
-rw-r--r--src/data/things/validators.js7
-rw-r--r--src/data/things/wiki-info.js4
-rw-r--r--src/data/yaml.js24
-rw-r--r--src/file-size-preloader.js5
-rw-r--r--src/find.js (renamed from src/util/find.js)4
-rw-r--r--src/gen-thumbs.js23
-rw-r--r--src/listing-spec.js8
-rw-r--r--src/page/album.js2
-rw-r--r--src/page/artist-alias.js3
-rw-r--r--src/page/artist.js7
-rw-r--r--src/page/flash.js5
-rw-r--r--src/page/group.js11
-rw-r--r--src/page/homepage.js13
-rw-r--r--src/page/index.js9
-rw-r--r--src/page/listing.js16
-rw-r--r--src/page/news.js2
-rw-r--r--src/page/static.js7
-rw-r--r--src/repl.js37
-rw-r--r--src/static/client2.js3
-rwxr-xr-xsrc/upd8.js93
-rw-r--r--src/url-spec.js2
-rw-r--r--src/util/html.js8
-rw-r--r--src/util/node-utils.js6
-rw-r--r--src/util/replacer.js13
-rw-r--r--src/util/serialize.js8
-rw-r--r--src/util/urls.js16
-rw-r--r--src/util/wiki-data.js7
-rw-r--r--src/write/bind-utilities.js74
-rw-r--r--src/write/build-modes/live-dev-server.js41
-rw-r--r--src/write/build-modes/static-build.js78
-rw-r--r--src/write/common-templates.js51
-rw-r--r--src/write/page-template.js635
-rw-r--r--src/write/validate-writes.js136
65 files changed, 327 insertions, 1260 deletions
diff --git a/src/content-function.js b/src/content-function.js
index 443715dc..a3d7b585 100644
--- a/src/content-function.js
+++ b/src/content-function.js
@@ -3,7 +3,7 @@ import {
   decorateErrorWithCause,
   empty,
   setIntersection,
-} from './util/sugar.js';
+} from '#sugar';
 
 export class ContentFunctionSpecError extends Error {}
 
diff --git a/src/content/dependencies/generateAlbumInfoPage.js b/src/content/dependencies/generateAlbumInfoPage.js
index 992f817a..ce17ab21 100644
--- a/src/content/dependencies/generateAlbumInfoPage.js
+++ b/src/content/dependencies/generateAlbumInfoPage.js
@@ -1,6 +1,7 @@
-import getChronologyRelations from '../util/getChronologyRelations.js';
-import {sortAlbumsTracksChronologically} from '#wiki-data';
 import {empty} from '#sugar';
+import {sortAlbumsTracksChronologically} from '#wiki-data';
+
+import getChronologyRelations from '../util/getChronologyRelations.js';
 
 export default {
   contentDependencies: [
diff --git a/src/content/dependencies/generateArtistGroupContributionsInfo.js b/src/content/dependencies/generateArtistGroupContributionsInfo.js
index bc3992e2..1aa5dce6 100644
--- a/src/content/dependencies/generateArtistGroupContributionsInfo.js
+++ b/src/content/dependencies/generateArtistGroupContributionsInfo.js
@@ -1,9 +1,4 @@
-import {
-  empty,
-  filterProperties,
-  stitchArrays,
-  unique,
-} from '#sugar';
+import {empty, filterProperties, stitchArrays, unique} from '#sugar';
 
 export default {
   contentDependencies: ['linkGroup'],
diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js
index 70106e30..334c5422 100644
--- a/src/content/dependencies/generateTrackInfoPage.js
+++ b/src/content/dependencies/generateTrackInfoPage.js
@@ -1,11 +1,7 @@
-import getChronologyRelations from '../util/getChronologyRelations.js';
-
-import {
-  sortAlbumsTracksChronologically,
-  sortFlashesChronologically,
-} from '#wiki-data';
-
 import {empty} from '#sugar';
+import {sortAlbumsTracksChronologically, sortFlashesChronologically} from '#wiki-data';
+
+import getChronologyRelations from '../util/getChronologyRelations.js';
 
 export default {
   contentDependencies: [
diff --git a/src/content/dependencies/index.js b/src/content/dependencies/index.js
index 60408293..3bc34845 100644
--- a/src/content/dependencies/index.js
+++ b/src/content/dependencies/index.js
@@ -1,13 +1,13 @@
-import chokidar from 'chokidar';
-import {ESLint} from 'eslint';
-
 import EventEmitter from 'node:events';
 import {readdir} from 'node:fs/promises';
 import * as path from 'node:path';
 import {fileURLToPath} from 'node:url';
 
-import contentFunction, {ContentFunctionSpecError} from '../../content-function.js';
-import {color, logWarn} from '../../util/cli.js';
+import chokidar from 'chokidar';
+import {ESLint} from 'eslint';
+
+import {color, logWarn} from '#cli';
+import contentFunction, {ContentFunctionSpecError} from '#content-function';
 import {annotateFunction} from '#sugar';
 
 function cachebust(filePath) {
diff --git a/src/content/dependencies/listAlbumsByDuration.js b/src/content/dependencies/listAlbumsByDuration.js
index 73e78140..1f95f5e3 100644
--- a/src/content/dependencies/listAlbumsByDuration.js
+++ b/src/content/dependencies/listAlbumsByDuration.js
@@ -1,11 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  filterByCount,
-  getTotalDuration,
-  sortAlphabetically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, getTotalDuration, sortAlphabetically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkAlbum'],
diff --git a/src/content/dependencies/listAlbumsByTracks.js b/src/content/dependencies/listAlbumsByTracks.js
index 1b925d2b..abf3c3ff 100644
--- a/src/content/dependencies/listAlbumsByTracks.js
+++ b/src/content/dependencies/listAlbumsByTracks.js
@@ -1,10 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  filterByCount,
-  sortAlphabetically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, sortAlphabetically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkAlbum'],
diff --git a/src/content/dependencies/listArtistsByCommentaryEntries.js b/src/content/dependencies/listArtistsByCommentaryEntries.js
index a041e0bd..4db9885e 100644
--- a/src/content/dependencies/listArtistsByCommentaryEntries.js
+++ b/src/content/dependencies/listArtistsByCommentaryEntries.js
@@ -1,9 +1,5 @@
 import {stitchArrays} from '#sugar';
-import {
-  filterByCount,
-  sortAlphabetically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, sortAlphabetically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkArtist'],
diff --git a/src/content/dependencies/listArtistsByContributions.js b/src/content/dependencies/listArtistsByContributions.js
index ee876d58..86c8cfa2 100644
--- a/src/content/dependencies/listArtistsByContributions.js
+++ b/src/content/dependencies/listArtistsByContributions.js
@@ -1,10 +1,5 @@
 import {stitchArrays, unique} from '#sugar';
-
-import {
-  filterByCount,
-  sortAlphabetically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, sortAlphabetically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkArtist'],
diff --git a/src/content/dependencies/listArtistsByDuration.js b/src/content/dependencies/listArtistsByDuration.js
index 5aeb8411..d6a18978 100644
--- a/src/content/dependencies/listArtistsByDuration.js
+++ b/src/content/dependencies/listArtistsByDuration.js
@@ -1,11 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  filterByCount,
-  getTotalDuration,
-  sortAlphabetically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, getTotalDuration, sortAlphabetically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkArtist'],
diff --git a/src/content/dependencies/listArtistsByName.js b/src/content/dependencies/listArtistsByName.js
index 2c85bb4f..6c0ad836 100644
--- a/src/content/dependencies/listArtistsByName.js
+++ b/src/content/dependencies/listArtistsByName.js
@@ -1,9 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  getArtistNumContributions,
-  sortAlphabetically,
-} from '#wiki-data';
+import {getArtistNumContributions, sortAlphabetically} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkArtist'],
diff --git a/src/content/dependencies/listGroupsByAlbums.js b/src/content/dependencies/listGroupsByAlbums.js
index 83fe5ce7..063b8269 100644
--- a/src/content/dependencies/listGroupsByAlbums.js
+++ b/src/content/dependencies/listGroupsByAlbums.js
@@ -1,9 +1,5 @@
 import {stitchArrays} from '#sugar';
-import {
-  filterByCount,
-  sortAlphabetically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, sortAlphabetically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkGroup'],
diff --git a/src/content/dependencies/listGroupsByDuration.js b/src/content/dependencies/listGroupsByDuration.js
index dce4846e..e2a023e6 100644
--- a/src/content/dependencies/listGroupsByDuration.js
+++ b/src/content/dependencies/listGroupsByDuration.js
@@ -1,11 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  filterByCount,
-  getTotalDuration,
-  sortAlphabetically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, getTotalDuration, sortAlphabetically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkGroup'],
diff --git a/src/content/dependencies/listGroupsByLatestAlbum.js b/src/content/dependencies/listGroupsByLatestAlbum.js
index 84012cf9..fa223664 100644
--- a/src/content/dependencies/listGroupsByLatestAlbum.js
+++ b/src/content/dependencies/listGroupsByLatestAlbum.js
@@ -1,11 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  compareDates,
-  filterMultipleArrays,
-  sortChronologically,
-  sortMultipleArrays,
-} from '#wiki-data';
+import {compareDates, filterMultipleArrays, sortChronologically, sortMultipleArrays} from '#wiki-data';
 
 export default {
   contentDependencies: [
diff --git a/src/content/dependencies/listGroupsByTracks.js b/src/content/dependencies/listGroupsByTracks.js
index e105b991..b3c55ca2 100644
--- a/src/content/dependencies/listGroupsByTracks.js
+++ b/src/content/dependencies/listGroupsByTracks.js
@@ -1,10 +1,5 @@
 import {accumulateSum, stitchArrays} from '#sugar';
-
-import {
-  filterByCount,
-  sortAlphabetically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, sortAlphabetically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkGroup'],
diff --git a/src/content/dependencies/listTagsByUses.js b/src/content/dependencies/listTagsByUses.js
index dae34048..98a50b89 100644
--- a/src/content/dependencies/listTagsByUses.js
+++ b/src/content/dependencies/listTagsByUses.js
@@ -1,10 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  filterByCount,
-  sortAlphabetically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, sortAlphabetically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkArtTag'],
diff --git a/src/content/dependencies/listTracksByDate.js b/src/content/dependencies/listTracksByDate.js
index 94646822..d6546e67 100644
--- a/src/content/dependencies/listTracksByDate.js
+++ b/src/content/dependencies/listTracksByDate.js
@@ -1,9 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  chunkByProperties,
-  sortAlbumsTracksChronologically,
-} from '#wiki-data';
+import {chunkByProperties, sortAlbumsTracksChronologically} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkAlbum', 'linkTrack'],
diff --git a/src/content/dependencies/listTracksByDurationInAlbum.js b/src/content/dependencies/listTracksByDurationInAlbum.js
index db30ed06..4e83e921 100644
--- a/src/content/dependencies/listTracksByDurationInAlbum.js
+++ b/src/content/dependencies/listTracksByDurationInAlbum.js
@@ -1,11 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  filterByCount,
-  filterMultipleArrays,
-  sortByCount,
-  sortChronologically,
-} from '#wiki-data';
+import {filterByCount, filterMultipleArrays, sortByCount, sortChronologically} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkAlbum', 'linkTrack'],
diff --git a/src/content/dependencies/listTracksByTimesReferenced.js b/src/content/dependencies/listTracksByTimesReferenced.js
index 3cb5a350..15a3461d 100644
--- a/src/content/dependencies/listTracksByTimesReferenced.js
+++ b/src/content/dependencies/listTracksByTimesReferenced.js
@@ -1,10 +1,5 @@
 import {stitchArrays} from '#sugar';
-
-import {
-  filterByCount,
-  sortAlbumsTracksChronologically,
-  sortByCount,
-} from '#wiki-data';
+import {filterByCount, sortAlbumsTracksChronologically, sortByCount} from '#wiki-data';
 
 export default {
   contentDependencies: ['generateListingPage', 'linkTrack'],
diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js
index 3d93284d..9a5ac456 100644
--- a/src/content/dependencies/transformContent.js
+++ b/src/content/dependencies/transformContent.js
@@ -1,7 +1,7 @@
-import {marked} from 'marked';
+import {bindFind} from '#find';
+import {parseInput} from '#replacer';
 
-import {bindFind} from '../../util/find.js';
-import {parseInput} from '../../util/replacer.js';
+import {marked} from 'marked';
 
 export const replacerSpec = {
   album: {
diff --git a/src/content/util/groupTracksByGroup.js b/src/content/util/groupTracksByGroup.js
index 559967bc..4e189007 100644
--- a/src/content/util/groupTracksByGroup.js
+++ b/src/content/util/groupTracksByGroup.js
@@ -1,4 +1,4 @@
-import {empty} from '../../util/sugar.js';
+import {empty} from '#sugar';
 
 export default function groupTracksByGroup(tracks, groups) {
   const lists = new Map(groups.map(group => [group, []]));
diff --git a/src/data/language.js b/src/data/language.js
index da9528f2..09466907 100644
--- a/src/data/language.js
+++ b/src/data/language.js
@@ -1,11 +1,10 @@
-import {readFile} from 'fs/promises';
+import {readFile} from 'node:fs/promises';
 
 // It stands for "HTML Entities", apparently. Cursed.
 import he from 'he';
 
-import T from './things/index.js';
+import T from '#things';
 
-// TODO: define somewhere besides upd8.js obviously
 export async function processLanguageFile(file) {
   const contents = await readFile(file, 'utf-8');
   const json = JSON.parse(contents);
diff --git a/src/data/things/album.js b/src/data/things/album.js
index 0c87b7c1..c012c243 100644
--- a/src/data/things/album.js
+++ b/src/data/things/album.js
@@ -1,7 +1,7 @@
-import Thing from './thing.js';
+import {empty} from '#sugar';
+import find from '#find';
 
-import {empty} from '../../util/sugar.js';
-import find from '../../util/find.js';
+import Thing from './thing.js';
 
 export class Album extends Thing {
   static [Thing.referenceType] = 'album';
diff --git a/src/data/things/art-tag.js b/src/data/things/art-tag.js
index 75fb0f7b..c103c4d5 100644
--- a/src/data/things/art-tag.js
+++ b/src/data/things/art-tag.js
@@ -1,8 +1,6 @@
-import Thing from './thing.js';
+import {sortAlbumsTracksChronologically} from '#wiki-data';
 
-import {
-  sortAlbumsTracksChronologically,
-} from '../../util/wiki-data.js';
+import Thing from './thing.js';
 
 export class ArtTag extends Thing {
   static [Thing.referenceType] = 'tag';
diff --git a/src/data/things/artist.js b/src/data/things/artist.js
index f144b21f..522ca5f9 100644
--- a/src/data/things/artist.js
+++ b/src/data/things/artist.js
@@ -1,6 +1,6 @@
-import Thing from './thing.js';
+import find from '#find';
 
-import find from '../../util/find.js';
+import Thing from './thing.js';
 
 export class Artist extends Thing {
   static [Thing.referenceType] = 'artist';
diff --git a/src/data/things/cacheable-object.js b/src/data/things/cacheable-object.js
index 6a210cc1..ea705a61 100644
--- a/src/data/things/cacheable-object.js
+++ b/src/data/things/cacheable-object.js
@@ -74,9 +74,9 @@
 //      function, which provides a mapping of exposed property names to whether
 //      or not their dependencies are yet met.
 
-import {color, ENABLE_COLOR} from '../../util/cli.js';
+import {inspect as nodeInspect} from 'node:util';
 
-import {inspect as nodeInspect} from 'util';
+import {color, ENABLE_COLOR} from '#cli';
 
 function inspect(value) {
   return nodeInspect(value, {colors: ENABLE_COLOR});
diff --git a/src/data/things/flash.js b/src/data/things/flash.js
index 1383fa83..6eb5234f 100644
--- a/src/data/things/flash.js
+++ b/src/data/things/flash.js
@@ -1,6 +1,6 @@
-import Thing from './thing.js';
+import find from '#find';
 
-import find from '../../util/find.js';
+import Thing from './thing.js';
 
 export class Flash extends Thing {
   static [Thing.referenceType] = 'flash';
diff --git a/src/data/things/group.js b/src/data/things/group.js
index 111d6715..ba339b3e 100644
--- a/src/data/things/group.js
+++ b/src/data/things/group.js
@@ -1,6 +1,6 @@
-import Thing from './thing.js';
+import find from '#find';
 
-import find from '../../util/find.js';
+import Thing from './thing.js';
 
 export class Group extends Thing {
   static [Thing.referenceType] = 'group';
diff --git a/src/data/things/homepage-layout.js b/src/data/things/homepage-layout.js
index a79dd77a..ec9e9556 100644
--- a/src/data/things/homepage-layout.js
+++ b/src/data/things/homepage-layout.js
@@ -1,6 +1,6 @@
-import Thing from './thing.js';
+import find from '#find';
 
-import find from '../../util/find.js';
+import Thing from './thing.js';
 
 export class HomepageLayout extends Thing {
   static [Thing.getPropertyDescriptors] = ({
diff --git a/src/data/things/index.js b/src/data/things/index.js
index 11b6b1a9..591cdc3b 100644
--- a/src/data/things/index.js
+++ b/src/data/things/index.js
@@ -1,12 +1,12 @@
-import {logError} from '../../util/cli.js';
-import {openAggregate, showAggregate} from '../../util/sugar.js';
+import * as path from 'node:path';
+import {fileURLToPath} from 'node:url';
 
-import * as path from 'path';
-import {fileURLToPath} from 'url';
+import {logError} from '#cli';
+import * as serialize from '#serialize';
+import {openAggregate, showAggregate} from '#sugar';
+import * as validators from '#validators';
 
 import Thing from './thing.js';
-import * as validators from './validators.js';
-import * as serialize from '../serialize.js';
 
 import * as albumClasses from './album.js';
 import * as artTagClasses from './art-tag.js';
@@ -20,6 +20,9 @@ import * as staticPageClasses from './static-page.js';
 import * as trackClasses from './track.js';
 import * as wikiInfoClasses from './wiki-info.js';
 
+export {default as Thing} from './thing.js';
+export {default as CacheableObject} from './cacheable-object.js';
+
 const allClassLists = {
   'album.js': albumClasses,
   'art-tag.js': artTagClasses,
diff --git a/src/data/things/thing.js b/src/data/things/thing.js
index 5004f4e6..c2876f56 100644
--- a/src/data/things/thing.js
+++ b/src/data/things/thing.js
@@ -1,7 +1,12 @@
 // Thing: base class for wiki data types, providing wiki-specific utility
 // functions on top of essential CacheableObject behavior.
 
-import CacheableObject from './cacheable-object.js';
+import {inspect} from 'node:util';
+
+import {color} from '#cli';
+import find from '#find';
+import {empty} from '#sugar';
+import {getKebabCase} from '#wiki-data';
 
 import {
   isAdditionalFileList,
@@ -19,14 +24,9 @@ import {
   validateInstanceOf,
   validateReference,
   validateReferenceList,
-} from './validators.js';
+} from '#validators';
 
-import {inspect} from 'util';
-import {color} from '../../util/cli.js';
-import {empty} from '../../util/sugar.js';
-import {getKebabCase} from '../../util/wiki-data.js';
-
-import find from '../../util/find.js';
+import CacheableObject from './cacheable-object.js';
 
 export default class Thing extends CacheableObject {
   static referenceType = Symbol('Thing.referenceType');
diff --git a/src/data/things/track.js b/src/data/things/track.js
index 36e3adbe..e176acb4 100644
--- a/src/data/things/track.js
+++ b/src/data/things/track.js
@@ -1,10 +1,10 @@
-import Thing from './thing.js';
+import {inspect} from 'node:util';
 
-import {inspect} from 'util';
+import {color} from '#cli';
+import find from '#find';
+import {empty} from '#sugar';
 
-import {color} from '../../util/cli.js';
-import find from '../../util/find.js';
-import {empty} from '../../util/sugar.js';
+import Thing from './thing.js';
 
 export class Track extends Thing {
   static [Thing.referenceType] = 'track';
@@ -47,7 +47,15 @@ export class Track extends Thing {
     hasCoverArt: {
       flags: {update: true, expose: true},
 
-      update: {validate: isBoolean},
+      update: {
+        validate(value) {
+          if (value !== false) {
+            throw new TypeError(`Expected false or null`);
+          }
+
+          return true;
+        },
+      },
 
       expose: {
         dependencies: ['albumData', 'coverArtistContribsByRef'],
diff --git a/src/data/things/validators.js b/src/data/things/validators.js
index 1754adf3..fc953c2a 100644
--- a/src/data/things/validators.js
+++ b/src/data/things/validators.js
@@ -1,8 +1,7 @@
-import {withAggregate} from '../../util/sugar.js';
+import {inspect as nodeInspect} from 'node:util';
 
-import {color, ENABLE_COLOR} from '../../util/cli.js';
-
-import {inspect as nodeInspect} from 'util';
+import {color, ENABLE_COLOR} from '#cli';
+import {withAggregate} from '#sugar';
 
 function inspect(value) {
   return nodeInspect(value, {colors: ENABLE_COLOR});
diff --git a/src/data/things/wiki-info.js b/src/data/things/wiki-info.js
index adf085e5..e906cab1 100644
--- a/src/data/things/wiki-info.js
+++ b/src/data/things/wiki-info.js
@@ -1,6 +1,6 @@
-import Thing from './thing.js';
+import find from '#find';
 
-import find from '../../util/find.js';
+import Thing from './thing.js';
 
 export class WikiInfo extends Thing {
   static [Thing.getPropertyDescriptors] = ({
diff --git a/src/data/yaml.js b/src/data/yaml.js
index c0058da3..35943199 100644
--- a/src/data/yaml.js
+++ b/src/data/yaml.js
@@ -1,15 +1,16 @@
 // yaml.js - specification for HSMusic YAML data file format and utilities for
-// loading and processing YAML files and documents
+// loading, processing, and validating YAML files and documents
 
-import * as path from 'path';
-import yaml from 'js-yaml';
-
-import {readFile, stat} from 'fs/promises';
-import {inspect as nodeInspect} from 'util';
+import {readFile, stat} from 'node:fs/promises';
+import * as path from 'node:path';
+import {inspect as nodeInspect} from 'node:util';
 
-import T from './things/index.js';
+import yaml from 'js-yaml';
 
-import {color, ENABLE_COLOR, logInfo, logWarn} from '../util/cli.js';
+import {color, ENABLE_COLOR, logInfo, logWarn} from '#cli';
+import find, {bindFind} from '#find';
+import {traverse} from '#node-utils';
+import T from '#things';
 
 import {
   conditionallySuppressError,
@@ -20,17 +21,14 @@ import {
   openAggregate,
   showAggregate,
   withAggregate,
-} from '../util/sugar.js';
+} from '#sugar';
 
 import {
   sortAlbumsTracksChronologically,
   sortAlphabetically,
   sortChronologically,
   sortFlashesChronologically,
-} from '../util/wiki-data.js';
-
-import find, {bindFind} from '../util/find.js';
-import {traverse} from '../util/node-utils.js';
+} from '#wiki-data';
 
 // --> General supporting stuff
 
diff --git a/src/file-size-preloader.js b/src/file-size-preloader.js
index ca1452d0..38e60e67 100644
--- a/src/file-size-preloader.js
+++ b/src/file-size-preloader.js
@@ -17,8 +17,9 @@
 // This only processes files one at a time because I'm lazy and stat calls
 // are very, very fast.
 
-import {stat} from 'fs/promises';
-import {logWarn} from './util/cli.js';
+import {stat} from 'node:fs/promises';
+
+import {logWarn} from '#cli';
 
 export default class FileSizePreloader {
   #paths = [];
diff --git a/src/util/find.js b/src/find.js
index dcb15b37..b8230800 100644
--- a/src/util/find.js
+++ b/src/find.js
@@ -1,6 +1,6 @@
-import {color, logWarn} from './cli.js';
+import {inspect} from 'node:util';
 
-import {inspect} from 'util';
+import {color, logWarn} from '#cli';
 
 function warnOrThrow(mode, message) {
   if (mode === 'error') {
diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js
index 82d5de3e..e9932822 100644
--- a/src/gen-thumbs.js
+++ b/src/gen-thumbs.js
@@ -85,18 +85,11 @@ const thumbnailSpec = {
   'small': {size: 250, quality: 85},
 };
 
-import {spawn} from 'child_process';
-import {createHash} from 'crypto';
-import * as path from 'path';
-
-import {
-  readFile,
-  stat,
-  unlink,
-  writeFile,
-} from 'fs/promises'; // Whatcha know! Nice.
-
-import {createReadStream} from 'fs'; // Still gotta import from 8oth tho, for createReadStream.
+import {spawn} from 'node:child_process';
+import {createHash} from 'node:crypto';
+import {createReadStream} from 'node:fs';
+import {readFile, stat, unlink, writeFile} from 'node:fs/promises';
+import * as path from 'node:path';
 
 import {
   color,
@@ -106,16 +99,16 @@ import {
   logWarn,
   parseOptions,
   progressPromiseAll,
-} from './util/cli.js';
+} from '#cli';
 
 import {
   commandExists,
   isMain,
   promisifyProcess,
   traverse,
-} from './util/node-utils.js';
+} from '#node-utils';
 
-import {delay, empty, queue} from './util/sugar.js';
+import {delay, empty, queue} from '#sugar';
 
 export const defaultMagickThreads = 8;
 
diff --git a/src/listing-spec.js b/src/listing-spec.js
index 1e21baa1..fe36fc01 100644
--- a/src/listing-spec.js
+++ b/src/listing-spec.js
@@ -1,8 +1,4 @@
-import {
-  accumulateSum,
-  empty,
-  showAggregate,
-} from './util/sugar.js';
+import {accumulateSum, empty, showAggregate} from '#sugar';
 
 import {
   chunkByProperties,
@@ -12,7 +8,7 @@ import {
   sortByDate,
   sortChronologically,
   sortFlashesChronologically,
-} from './util/wiki-data.js';
+} from '#wiki-data';
 
 const listingSpec = [];
 
diff --git a/src/page/album.js b/src/page/album.js
index 3b2d02d2..69fcabcf 100644
--- a/src/page/album.js
+++ b/src/page/album.js
@@ -1,5 +1,3 @@
-// Album page specification.
-
 export const description = `per-album info, artwork gallery & commentary pages`;
 
 export function targets({wikiData}) {
diff --git a/src/page/artist-alias.js b/src/page/artist-alias.js
index 23513ce1..1da2af41 100644
--- a/src/page/artist-alias.js
+++ b/src/page/artist-alias.js
@@ -1,6 +1,3 @@
-// Artist alias redirect pages.
-// (Makes old permalinks bring visitors to the up-to-date page.)
-
 export const description = `redirects for aliased artist names`;
 
 export function targets({wikiData}) {
diff --git a/src/page/artist.js b/src/page/artist.js
index c53a4913..27ff8961 100644
--- a/src/page/artist.js
+++ b/src/page/artist.js
@@ -1,11 +1,8 @@
-// Artist page specification.
-//
-// NB: See artist-alias.js for artist alias redirect pages.
-
-import {empty} from '../util/sugar.js';
+import {empty} from '#sugar';
 
 export const description = `per-artist info & artwork gallery pages`;
 
+// NB: See artist-alias.js for artist alias redirect pages.
 export function targets({wikiData}) {
   return wikiData.artistData;
 }
diff --git a/src/page/flash.js b/src/page/flash.js
index 14e67d8d..b9d27d0f 100644
--- a/src/page/flash.js
+++ b/src/page/flash.js
@@ -1,7 +1,4 @@
-// Flash page and index specifications.
-
-import {empty} from '../util/sugar.js';
-import {getFlashLink} from '../util/wiki-data.js';
+import {empty} from '#sugar';
 
 export const description = `flash & game pages`;
 
diff --git a/src/page/group.js b/src/page/group.js
index fa6c1c97..b0ed5baf 100644
--- a/src/page/group.js
+++ b/src/page/group.js
@@ -1,13 +1,4 @@
-// Group page specifications.
-
-import {
-  empty,
-} from '../util/sugar.js';
-
-import {
-  getTotalDuration,
-  sortChronologically,
-} from '../util/wiki-data.js';
+import {empty} from '#sugar';
 
 export const description = `per-group info & album gallery pages`;
 
diff --git a/src/page/homepage.js b/src/page/homepage.js
index 15dcadd1..53ee6e46 100644
--- a/src/page/homepage.js
+++ b/src/page/homepage.js
@@ -1,16 +1,3 @@
-// Homepage specification.
-
-import {
-  bindOpts,
-  empty,
-  withEntries,
-} from '../util/sugar.js';
-
-import {
-  getNewAdditions,
-  getNewReleases,
-} from '../util/wiki-data.js';
-
 export const description = `main wiki homepage`;
 
 export function pathsTargetless({wikiData}) {
diff --git a/src/page/index.js b/src/page/index.js
index e62241d9..48e22d2e 100644
--- a/src/page/index.js
+++ b/src/page/index.js
@@ -1,12 +1,3 @@
-// NB: This is the index for the page/ directory and contains exports for all
-// other modules here! It's not the page spec for the homepage - see
-// homepage.js for that.
-//
-// (TODO: The docs here from initial draft were totally outdated.
-//        We don't have docs for the new setup yet.
-//        Write those!!)
-//
-
 export * as album from './album.js';
 export * as artist from './artist.js';
 export * as artistAlias from './artist-alias.js';
diff --git a/src/page/listing.js b/src/page/listing.js
index 64db413d..bb22c21f 100644
--- a/src/page/listing.js
+++ b/src/page/listing.js
@@ -1,19 +1,13 @@
-// Listing page specification.
-//
+export const description = `wiki-wide listing pages & index`;
+
 // The targets here are a bit different than for most pages: rather than data
 // objects loaded from text files in the wiki data directory, they're hard-
-// coded specifications, with various JS functions for processing wiki data
-// and turning it into user-readable HTML listings.
+// coded specifications, each directly identifying the hard-coded content
+// function used to generate that listing.
 //
 // Individual listing specs are described in src/listing-spec.js, but are
 // provided via wikiData like other (normal) data objects.
-
-import {empty} from '../util/sugar.js';
-
-import {getTotalDuration} from '../util/wiki-data.js';
-
-export const description = `wiki-wide listing pages & index`;
-
+//
 export function targets({wikiData}) {
   return (
     wikiData.listingSpec
diff --git a/src/page/news.js b/src/page/news.js
index 4928a116..194ffdcb 100644
--- a/src/page/news.js
+++ b/src/page/news.js
@@ -1,5 +1,3 @@
-// News entry & index page specifications.
-
 export const description = `per-entry news pages & index`;
 
 export function condition({wikiData}) {
diff --git a/src/page/static.js b/src/page/static.js
index 82330dec..c9d806ff 100644
--- a/src/page/static.js
+++ b/src/page/static.js
@@ -1,9 +1,8 @@
-// Static content page specification. (These are static pages coded into the
-// wiki data folder, used for a variety of purposes, e.g. wiki info,
-// changelog, and so on.)
-
 export const description = `static wiki-wide content pages specified in data`;
 
+// Static pages are written in the wiki's data folder and contain content and
+// basic page metadata. They're used for a variety of purposes, such as an
+// "about" page, a changelog, links to places beyond the wiki, and so on.
 export function targets({wikiData}) {
   return wikiData.staticPageData;
 }
diff --git a/src/repl.js b/src/repl.js
index 6b6a405e..9ab4ddf0 100644
--- a/src/repl.js
+++ b/src/repl.js
@@ -1,23 +1,20 @@
-import * as os from 'os';
-import * as path from 'path';
-import * as repl from 'repl';
-import {fileURLToPath} from 'url';
-
-import thingConstructors from './data/things/index.js';
-import {quickLoadAllFromYAML} from './data/yaml.js';
-import {logError, logWarn, parseOptions} from './util/cli.js';
-import {isMain} from './util/node-utils.js';
-import {bindOpts, showAggregate} from './util/sugar.js';
-import {generateURLs} from './util/urls.js';
-
-import {processLanguageFile} from './data/language.js';
-
-import * as serialize from './util/serialize.js';
-import * as sugar from './util/sugar.js';
-import * as wikiDataUtils from './util/wiki-data.js';
-import _find, {bindFind} from './util/find.js';
-
-import urlSpec from './url-spec.js';
+import * as os from 'node:os';
+import * as path from 'node:path';
+import * as repl from 'node:repl';
+import {fileURLToPath} from 'node:url';
+
+import {logError, logWarn, parseOptions} from '#cli';
+import {isMain} from '#node-utils';
+import {processLanguageFile} from '#language';
+import {bindOpts, showAggregate} from '#sugar';
+import {generateURLs, urlSpec} from '#urls';
+import {quickLoadAllFromYAML} from '#yaml';
+
+import _find, {bindFind} from '#find';
+import thingConstructors from '#things';
+import * as serialize from '#serialize';
+import * as sugar from '#sugar';
+import * as wikiDataUtils from '#wiki-data';
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
 
diff --git a/src/static/client2.js b/src/static/client2.js
index 36dba2d1..0cdb8b0e 100644
--- a/src/static/client2.js
+++ b/src/static/client2.js
@@ -4,11 +4,8 @@
 // the random track feature right now - the idea is we only use it for stuff
 // that cannot 8e done at static-site compile time, 8y its fundamentally
 // ephemeral nature.
-//
-// Upd8: As of 04/02/2021, it's now used for info cards too! Nice.
 
 import {getColors} from '../util/colors.js';
-
 import {getArtistNumContributions} from '../util/wiki-data.js';
 
 let albumData, artistData;
diff --git a/src/upd8.js b/src/upd8.js
index f5781272..bfdd1c2a 100755
--- a/src/upd8.js
+++ b/src/upd8.js
@@ -31,25 +31,37 @@
 // Oh yeah, like. Just run this through some relatively recent version of
 // node.js and you'll 8e fine. ...Within the project root. O8viously.
 
-import {execSync} from 'child_process';
-import * as path from 'path';
-import {fileURLToPath} from 'url';
+import {execSync} from 'node:child_process';
+import * as path from 'node:path';
+import {fileURLToPath} from 'node:url';
+
 import wrap from 'word-wrap';
 
+import {processLanguageFile} from '#language';
+import {isMain, traverse} from '#node-utils';
+import bootRepl from '#repl';
+import {empty, showAggregate, withEntries} from '#sugar';
+import {CacheableObject} from '#things';
+import {generateURLs, urlSpec} from '#urls';
+import {sortByName} from '#wiki-data';
+
+import {
+  color,
+  decorateTime,
+  logWarn,
+  logInfo,
+  logError,
+  parseOptions,
+  progressCallAll,
+  progressPromiseAll,
+} from '#cli';
+
 import genThumbs, {
   clearThumbs,
   defaultMagickThreads,
   isThumb,
   verifyImagePaths,
-} from './gen-thumbs.js';
-
-import bootRepl from './repl.js';
-import {listingSpec, listingTargetSpec} from './listing-spec.js';
-import urlSpec from './url-spec.js';
-
-import {processLanguageFile} from './data/language.js';
-
-import CacheableObject from './data/things/cacheable-object.js';
+} from '#thumbs';
 
 import {
   filterDuplicateDirectories,
@@ -58,28 +70,11 @@ import {
   loadAndProcessDataDocuments,
   sortWikiDataArrays,
   WIKI_INFO_FILE,
-} from './data/yaml.js';
-
-import {isMain, traverse} from './util/node-utils.js';
-import {empty, showAggregate, withEntries} from './util/sugar.js';
-import {generateURLs} from './util/urls.js';
-import {sortByName} from './util/wiki-data.js';
-
-import {generateDevelopersCommentHTML} from './write/page-template.js';
-import * as buildModes from './write/build-modes/index.js';
-
-import {
-  color,
-  decorateTime,
-  logWarn,
-  logInfo,
-  logError,
-  parseOptions,
-  progressCallAll,
-  progressPromiseAll,
-} from './util/cli.js';
+} from '#yaml';
 
 import FileSizePreloader from './file-size-preloader.js';
+import {listingSpec, listingTargetSpec} from './listing-spec.js';
+import * as buildModes from './write/build-modes/index.js';
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
 
@@ -725,11 +720,35 @@ async function main() {
 
   if (noBuild) return;
 
-  const developersComment = generateDevelopersCommentHTML({
-    buildTime: BUILD_TIME,
-    commit: COMMIT,
-    wikiData,
-  });
+  const developersComment =
+    `<!--\n` + [
+      wikiData.wikiInfo.canonicalBase
+        ? `hsmusic.wiki - ${wikiData.wikiInfo.name}, ${wikiData.wikiInfo.canonicalBase}`
+        : `hsmusic.wiki - ${wikiData.wikiInfo.name}`,
+      'Code copyright 2019-2023 Quasar Nebula et al (MIT License)',
+      ...wikiData.wikiInfo.canonicalBase === 'https://hsmusic.wiki/' ? [
+        'Data avidly compiled and localization brought to you',
+        'by our awesome team and community of wiki contributors',
+        '***',
+        'Want to contribute? Join our Discord or leave feedback!',
+        '- https://hsmusic.wiki/discord/',
+        '- https://hsmusic.wiki/feedback/',
+        '- https://github.com/hsmusic/',
+      ] : [
+        'https://github.com/hsmusic/',
+      ],
+      '***',
+      BUILD_TIME &&
+        `Site built: ${BUILD_TIME.toLocaleString('en-US', {
+          dateStyle: 'long',
+          timeStyle: 'long',
+        })}`,
+      COMMIT &&
+        `Latest code commit: ${COMMIT}`,
+    ]
+      .filter(Boolean)
+      .map(line => `    ` + line)
+      .join('\n') + `\n-->`;
 
   return selectedBuildMode.go({
     cliOptions,
diff --git a/src/url-spec.js b/src/url-spec.js
index 8340f196..4d103134 100644
--- a/src/url-spec.js
+++ b/src/url-spec.js
@@ -1,4 +1,4 @@
-import {withEntries} from './util/sugar.js';
+import {withEntries} from '#sugar';
 
 const urlSpec = {
   data: {
diff --git a/src/util/html.js b/src/util/html.js
index 5687bba3..7ed363c0 100644
--- a/src/util/html.js
+++ b/src/util/html.js
@@ -1,9 +1,9 @@
-// Some really simple functions for formatting HTML content.
+// Some really, really simple functions for formatting HTML content.
 
-import {inspect} from 'util';
+import {inspect} from 'node:util';
 
-import * as commonValidators from '../data/things/validators.js';
-import {empty} from './sugar.js';
+import {empty} from '#sugar';
+import * as commonValidators from '#validators';
 
 // COMPREHENSIVE!
 // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
diff --git a/src/util/node-utils.js b/src/util/node-utils.js
index 2fb7e8dd..345d10aa 100644
--- a/src/util/node-utils.js
+++ b/src/util/node-utils.js
@@ -1,8 +1,8 @@
 // Utility functions which are only relevant to particular Node.js constructs.
 
-import {readdir, stat} from 'fs/promises';
-import {fileURLToPath} from 'url';
-import * as path from 'path';
+import {readdir, stat} from 'node:fs/promises';
+import * as path from 'node:path';
+import {fileURLToPath} from 'node:url';
 
 import _commandExists from 'command-exists';
 
diff --git a/src/util/replacer.js b/src/util/replacer.js
index 278ffab5..c5289cc5 100644
--- a/src/util/replacer.js
+++ b/src/util/replacer.js
@@ -1,6 +1,13 @@
-import {logError, logWarn} from './cli.js';
-import {escapeRegex} from './sugar.js';
-import * as html from './html.js';
+// Regex-based forward parser for wiki content, breaking up text input into
+// text and (possibly nested) tag nodes.
+//
+// The behavior here is quite tied into the `transformContent` content
+// function, which converts nodes parsed here into actual HTML, links, etc
+// for embedding in a wiki webpage.
+
+import {logError, logWarn} from '#cli';
+import * as html from '#html';
+import {escapeRegex} from '#sugar';
 
 // Syntax literals.
 const tagBeginning = '[[';
diff --git a/src/util/serialize.js b/src/util/serialize.js
index 73a31374..4992e2bf 100644
--- a/src/util/serialize.js
+++ b/src/util/serialize.js
@@ -1,3 +1,10 @@
+// Utils used when per-wiki-object data files.
+// Retained for reference and/or later reorganization.
+//
+// Not to be confused with data/serialize.js, which provides a generic
+// interface for serializing any Thing object.
+
+/*
 export function serializeLink(thing) {
   const ret = {};
   ret.name = thing.name;
@@ -67,3 +74,4 @@ export function serializeGroupsForTrack(track, {serializeLink}) {
     urls: group.urls,
   }));
 }
+*/
diff --git a/src/util/urls.js b/src/util/urls.js
index 02961743..d2b303e9 100644
--- a/src/util/urls.js
+++ b/src/util/urls.js
@@ -4,9 +4,19 @@
 // which can really quickly take su8stitute parameters to link from any one
 // place to another; 8ut there are also a few other utilities, too.
 
-import * as path from 'path';
-
-import {withEntries} from './sugar.js';
+import * as path from 'node:path';
+
+import {withEntries} from '#sugar';
+
+// This export is only provided for convenience, i.e. to enable the following:
+//
+//   import {urlSpec} from '#urls';
+//
+// It's not actually defined in this module's variable scope, and functions
+// exported here require a urlSpec (whether this default one or another) to be
+// passed directly.
+//
+export {default as urlSpec} from '../url-spec.js';
 
 export function generateURLs(urlSpec) {
   const getValueForFullKey = (obj, fullKey) => {
diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js
index 6930496f..ad2f82fb 100644
--- a/src/util/wiki-data.js
+++ b/src/util/wiki-data.js
@@ -1,11 +1,6 @@
 // Utility functions for interacting with wiki data.
 
-import {
-  accumulateSum,
-  empty,
-  stitchArrays,
-  unique,
-} from './sugar.js';
+import {accumulateSum, empty, stitchArrays, unique} from './sugar.js';
 
 // Generic value operations
 
diff --git a/src/write/bind-utilities.js b/src/write/bind-utilities.js
index 2ddc2b38..8e2adea7 100644
--- a/src/write/bind-utilities.js
+++ b/src/write/bind-utilities.js
@@ -4,12 +4,11 @@
 
 import chroma from 'chroma-js';
 
-import * as html from '../util/html.js';
-
-import {bindOpts} from '../util/sugar.js';
-import {getColors} from '../util/colors.js';
-import {bindFind} from '../util/find.js';
-import {thumb} from '../util/urls.js';
+import {getColors} from '#colors';
+import {bindFind} from '#find';
+import * as html from '#html';
+import {bindOpts} from '#sugar';
+import {thumb} from '#urls';
 
 export function bindUtilities({
   absoluteTo,
@@ -24,9 +23,6 @@ export function bindUtilities({
   urls,
   wikiData,
 }) {
-  // TODO: Is there some nicer way to define these,
-  // may8e without totally re-8inding everything for
-  // each page?
   const bound = {};
 
   Object.assign(bound, {
@@ -50,65 +46,5 @@ export function bindUtilities({
 
   bound.find = bindFind(wikiData, {mode: 'warn'});
 
-  /*
-  bound.generateNavigationLinks = bindOpts(generateNavigationLinks, {
-    link: bound.link,
-    language,
-  });
-
-  bound.generateStickyHeadingContainer = bindOpts(generateStickyHeadingContainer, {
-    [bindOpts.bindIndex]: 0,
-    html,
-    img: bound.img,
-  });
-
-  bound.generateChronologyLinks = bindOpts(generateChronologyLinks, {
-    html,
-    language,
-    link: bound.link,
-    wikiData,
-
-    generateNavigationLinks: bound.generateNavigationLinks,
-  });
-
-  bound.generateInfoGalleryLinks = bindOpts(generateInfoGalleryLinks, {
-    [bindOpts.bindIndex]: 2,
-    link: bound.link,
-    language,
-  });
-
-  bound.getGridHTML = bindOpts(getGridHTML, {
-    [bindOpts.bindIndex]: 0,
-    img: bound.img,
-    html,
-    language,
-
-    getRevealStringFromArtTags: bound.getRevealStringFromArtTags,
-  });
-
-  bound.getAlbumGridHTML = bindOpts(getAlbumGridHTML, {
-    [bindOpts.bindIndex]: 0,
-    link: bound.link,
-    language,
-
-    getAlbumCover: bound.getAlbumCover,
-    getGridHTML: bound.getGridHTML,
-  });
-
-  bound.getFlashGridHTML = bindOpts(getFlashGridHTML, {
-    [bindOpts.bindIndex]: 0,
-    link: bound.link,
-
-    getFlashCover: bound.getFlashCover,
-    getGridHTML: bound.getGridHTML,
-  });
-
-  bound.getCarouselHTML = bindOpts(getCarouselHTML, {
-    [bindOpts.bindIndex]: 0,
-    img: bound.img,
-    html,
-  });
-  */
-
   return bound;
 }
diff --git a/src/write/build-modes/live-dev-server.js b/src/write/build-modes/live-dev-server.js
index edee3267..2767a02f 100644
--- a/src/write/build-modes/live-dev-server.js
+++ b/src/write/build-modes/live-dev-server.js
@@ -1,35 +1,26 @@
-import * as http from 'http';
-import {createReadStream} from 'fs';
-import {stat} from 'fs/promises';
-import * as path from 'path';
-import {pipeline} from 'stream/promises'
+import * as http from 'node:http';
+import {createReadStream} from 'node:fs';
+import {stat} from 'node:fs/promises';
+import * as path from 'node:path';
+import {pipeline} from 'node:stream/promises'
+
+import {logInfo, logWarn, progressCallAll} from '#cli';
+import {watchContentDependencies} from '#content-dependencies';
+import {quickEvaluate} from '#content-function';
+import * as html from '#html';
+import * as pageSpecs from '#page-specs';
+import {serializeThings} from '#serialize';
+import {empty} from '#sugar';
 
-import {bindUtilities} from '../bind-utilities.js';
-
-import {serializeThings} from '../../data/serialize.js';
-
-import * as pageSpecs from '../../page/index.js';
-
-import {logInfo, logWarn, progressCallAll} from '../../util/cli.js';
-import * as html from '../../util/html.js';
-import {empty} from '../../util/sugar.js';
 import {
   getPagePathname,
   getPagePathnameAcrossLanguages,
   getURLsFrom,
   getURLsFromRoot,
-} from '../../util/urls.js';
+} from '#urls';
 
-import {
-  generateGlobalWikiDataJSON,
-  generateRedirectHTML,
-} from '../page-template.js';
-
-import {
-  watchContentDependencies,
-} from '../../content/dependencies/index.js';
-
-import {quickEvaluate} from '../../content-function.js';
+import {bindUtilities} from '../bind-utilities.js';
+import {generateGlobalWikiDataJSON, generateRedirectHTML} from '../common-templates.js';
 
 const defaultHost = '0.0.0.0';
 const defaultPort = 8002;
diff --git a/src/write/build-modes/static-build.js b/src/write/build-modes/static-build.js
index 4f074058..2210dfe7 100644
--- a/src/write/build-modes/static-build.js
+++ b/src/write/build-modes/static-build.js
@@ -1,20 +1,20 @@
-import * as path from 'path';
-
-import {bindUtilities} from '../bind-utilities.js';
-// import {validateWrites} from '../validate-writes.js';
+import * as path from 'node:path';
 
 import {
-  quickLoadContentDependencies,
-} from '../../content/dependencies/index.js';
-
-import {quickEvaluate} from '../../content-function.js';
-
-import {serializeThings} from '../../data/serialize.js';
-
-import * as pageSpecs from '../../page/index.js';
+  copyFile,
+  mkdir,
+  stat,
+  symlink,
+  writeFile,
+  unlink,
+} from 'node:fs/promises';
 
-import * as html from '../../util/html.js';
-import {empty, queue, withEntries} from '../../util/sugar.js';
+import {quickLoadContentDependencies} from '#content-dependencies';
+import {quickEvaluate} from '#content-function';
+import * as html from '#html';
+import * as pageSpecs from '#page-specs';
+import {serializeThings} from '#serialize';
+import {empty, queue, withEntries} from '#sugar';
 
 import {
   logError,
@@ -22,14 +22,17 @@ import {
   logWarn,
   progressCallAll,
   progressPromiseAll,
-} from '../../util/cli.js';
+} from '#cli';
 
 import {
   getPagePathname,
   getPagePathnameAcrossLanguages,
   getURLsFrom,
   getURLsFromRoot,
-} from '../../util/urls.js';
+} from '#urls';
+
+import {bindUtilities} from '../bind-utilities.js';
+import {generateRedirectHTML, generateGlobalWikiDataJSON} from '../common-templates.js';
 
 const pageFlags = Object.keys(pageSpecs);
 
@@ -78,36 +81,6 @@ export function getCLIOptions() {
   };
 }
 
-function generateRedirectHTML(title, target, {language}) {
-  return `<!DOCTYPE html>\n` + html.tag('html', [
-    html.tag('head', [
-      html.tag('title', language.$('redirectPage.title', {title})),
-      html.tag('meta', {charset: 'utf-8'}),
-
-      html.tag('meta', {
-        'http-equiv': 'refresh',
-        content: `0;url=${target}`,
-      }),
-
-      // TODO: Is this OK for localized pages?
-      html.tag('link', {
-        rel: 'canonical',
-        href: target,
-      }),
-    ]),
-
-    html.tag('body',
-      html.tag('main', [
-        html.tag('h1',
-          language.$('redirectPage.title', {title})),
-        html.tag('p',
-          language.$('redirectPage.infoLine', {
-            target: html.tag('a', {href: target}, target),
-          })),
-      ])),
-  ]);
-}
-
 export async function go({
   cliOptions,
   _dataPath,
@@ -173,12 +146,10 @@ export async function go({
     outputPath,
     urls,
     wikiData,
-    /*
     wikiDataJSON: generateGlobalWikiDataJSON({
       serializeThings,
       wikiData,
-    })
-    */
+    }),
   });
 
   const buildSteps = writeAll
@@ -409,15 +380,6 @@ async function wrapLanguages(fn, {
   }
 }
 
-import {
-  copyFile,
-  mkdir,
-  stat,
-  symlink,
-  writeFile,
-  unlink,
-} from 'fs/promises';
-
 async function writePage({
   pageHTML,
   oEmbedJSON = '',
diff --git a/src/write/common-templates.js b/src/write/common-templates.js
new file mode 100644
index 00000000..2dd4c924
--- /dev/null
+++ b/src/write/common-templates.js
@@ -0,0 +1,51 @@
+import * as html from '#html';
+
+export function generateRedirectHTML(title, target, {language}) {
+  return `<!DOCTYPE html>\n` + html.tag('html', [
+    html.tag('head', [
+      html.tag('title', language.$('redirectPage.title', {title})),
+      html.tag('meta', {charset: 'utf-8'}),
+
+      html.tag('meta', {
+        'http-equiv': 'refresh',
+        content: `0;url=${target}`,
+      }),
+
+      // TODO: Is this OK for localized pages?
+      html.tag('link', {
+        rel: 'canonical',
+        href: target,
+      }),
+    ]),
+
+    html.tag('body',
+      html.tag('main', [
+        html.tag('h1',
+          language.$('redirectPage.title', {title})),
+        html.tag('p',
+          language.$('redirectPage.infoLine', {
+            target: html.tag('a', {href: target}, target),
+          })),
+      ])),
+  ]);
+}
+
+export function generateGlobalWikiDataJSON({
+  serializeThings,
+  wikiData,
+}) {
+  const stringifyThings = thingData =>
+    JSON.stringify(serializeThings(thingData));
+
+  return '{\n' +
+    ([
+      `"albumData": ${stringifyThings(wikiData.albumData)},`,
+      wikiData.wikiInfo.enableFlashesAndGames &&
+        `"flashData": ${stringifyThings(wikiData.flashData)},`,
+      `"artistData": ${stringifyThings(wikiData.artistData)}`,
+    ]
+      .filter(Boolean)
+      .map(line => '  ' + line)
+      .join('\n')) +
+    '\n}';
+}
diff --git a/src/write/page-template.js b/src/write/page-template.js
deleted file mode 100644
index d3d7b098..00000000
--- a/src/write/page-template.js
+++ /dev/null
@@ -1,635 +0,0 @@
-import chroma from 'chroma-js';
-
-import * as html from '../util/html.js';
-import {getColors} from '../util/colors.js';
-
-export function generateDevelopersCommentHTML({
-  buildTime,
-  commit,
-  wikiData,
-}) {
-  const {name, canonicalBase} = wikiData.wikiInfo;
-  return `<!--\n` + [
-    canonicalBase
-      ? `hsmusic.wiki - ${name}, ${canonicalBase}`
-      : `hsmusic.wiki - ${name}`,
-    'Code copyright 2019-2023 Quasar Nebula et al (MIT License)',
-    ...canonicalBase === 'https://hsmusic.wiki/' ? [
-      'Data avidly compiled and localization brought to you',
-      'by our awesome team and community of wiki contributors',
-      '***',
-      'Want to contribute? Join our Discord or leave feedback!',
-      '- https://hsmusic.wiki/discord/',
-      '- https://hsmusic.wiki/feedback/',
-      '- https://github.com/hsmusic/',
-    ] : [
-      'https://github.com/hsmusic/',
-    ],
-    '***',
-    buildTime &&
-      `Site built: ${buildTime.toLocaleString('en-US', {
-        dateStyle: 'long',
-        timeStyle: 'long',
-      })}`,
-    commit &&
-      `Latest code commit: ${commit}`,
-  ]
-    .filter(Boolean)
-    .map(line => `    ` + line)
-    .join('\n') + `\n-->`;
-}
-
-export function generateDocumentHTML(pageInfo, {
-  cachebust,
-  defaultLanguage,
-  developersComment,
-  generateCoverLink,
-  generateStickyHeadingContainer,
-  img,
-  getThemeString,
-  language,
-  languages,
-  localizedPathnames,
-  oEmbedJSONHref,
-  pagePath,
-  pathname,
-  to,
-  transformMultiline,
-  wikiData,
-}) {
-  const {wikiInfo} = wikiData;
-
-  let {
-    title = '',
-    meta = {},
-    theme = '',
-    stylesheet = '',
-
-    showWikiNameInTitle = true,
-    themeColor = '',
-
-    // missing properties are auto-filled, see below!
-    body = {},
-    banner = {},
-    cover = {},
-    main = {},
-    sidebarLeft = {},
-    sidebarRight = {},
-    nav = {},
-    secondaryNav = {},
-    footer = {},
-    socialEmbed = {},
-  } = pageInfo;
-
-  body ||= {};
-  body.style ??= '';
-
-  theme ||= getThemeString(wikiInfo.color);
-
-  banner ||= {};
-  banner.classes ??= [];
-  banner.src ??= '';
-  banner.position ??= '';
-  banner.dimensions ??= [0, 0];
-
-  main ||= {};
-  main.classes ??= [];
-  main.content ??= '';
-  main.headingMode ??= 'none';
-
-  cover ||= {};
-  cover.src ??= '';
-  cover.alt ??= '';
-  cover.artTags ??= [];
-
-  sidebarLeft ||= {};
-  sidebarRight ||= {};
-
-  for (const sidebar of [sidebarLeft, sidebarRight]) {
-    sidebar.classes ??= [];
-    sidebar.content ??= '';
-    sidebar.collapse ??= true;
-  }
-
-  nav ||= {};
-  nav.classes ??= [];
-  nav.content ??= '';
-  nav.bottomRowContent ??= '';
-  nav.links ??= [];
-  nav.linkContainerClasses ??= [];
-
-  secondaryNav ||= {};
-  secondaryNav.content ??= '';
-  secondaryNav.content ??= '';
-
-  footer ||= {};
-  footer.classes ??= [];
-  footer.content ??= wikiInfo.footerContent
-    ? transformMultiline(wikiInfo.footerContent)
-    : '';
-
-  socialEmbed ||= {};
-
-  const colors = themeColor
-    ? getColors(themeColor, {chroma})
-    : null;
-
-  const canonical = wikiInfo.canonicalBase
-    ? wikiInfo.canonicalBase + (pathname === '/' ? '' : pathname)
-    : '';
-
-  const localizedCanonical = wikiInfo.canonicalBase
-    ? Object.entries(localizedPathnames).map(([code, pathname]) => ({
-        lang: code,
-        href: wikiInfo.canonicalBase + (pathname === '/' ? '' : pathname),
-      }))
-    : [];
-
-  const collapseSidebars =
-    sidebarLeft.collapse !== false && sidebarRight.collapse !== false;
-
-
-
-  const footerHTML =
-    html.tag('footer',
-      {
-        [html.onlyIfContent]: true,
-        id: 'footer',
-        class: footer.classes,
-      },
-      [
-        html.tag('div',
-          {
-            [html.onlyIfContent]: true,
-            class: 'footer-content',
-          },
-          footer.content),
-
-        getFooterLocalizationLinks({
-          defaultLanguage,
-          html,
-          language,
-          languages,
-          pagePath,
-          to,
-        }),
-      ]);
-
-  const generateSidebarHTML = (id, {
-    content,
-    multiple,
-    classes,
-    collapse = true,
-    wide = false,
-
-    // 'last' - last or only sidebar box is sticky
-    // 'column' - entire column, incl. multiple boxes from top, is sticky
-    // 'none' - sidebar not sticky at all, stays at top of page
-    stickyMode = 'last',
-  }) =>
-    content
-      ? html.tag('div',
-          {
-            id,
-            class: [
-              'sidebar-column',
-              'sidebar',
-              wide && 'wide',
-              !collapse && 'no-hide',
-              stickyMode !== 'none' && 'sticky-' + stickyMode,
-              ...classes,
-            ],
-          },
-          content)
-      : multiple
-      ? html.tag('div',
-          {
-            [html.onlyIfContent]: true,
-            id,
-            class: [
-              'sidebar-column',
-              'sidebar-multiple',
-              wide && 'wide',
-              !collapse && 'no-hide',
-              stickyMode !== 'none' && 'sticky-' + stickyMode,
-            ],
-          },
-          multiple
-            .filter(Boolean)
-            .map((infoOrContent) =>
-              (typeof infoOrContent === 'object' && !Array.isArray(infoOrContent))
-                ? infoOrContent
-                : {content: infoOrContent})
-            .filter(({content}) => content)
-            .map(({
-              content,
-              classes: classes2 = [],
-            }) =>
-              html.tag('div',
-                {
-                  class: ['sidebar', ...classes, ...classes2],
-                },
-                html.fragment(content))))
-      : '';
-
-  const sidebarLeftHTML = generateSidebarHTML('sidebar-left', sidebarLeft);
-  const sidebarRightHTML = generateSidebarHTML('sidebar-right', sidebarRight);
-
-  if (nav.simple) {
-    nav.linkContainerClasses = ['nav-links-hierarchy'];
-    nav.links = [{toHome: true}, {toCurrentPage: true}];
-  }
-
-  const links = (nav.links || []).filter(Boolean);
-
-  const navLinkParts = [];
-  for (let i = 0; i < links.length; i++) {
-    let cur = links[i];
-
-    let {title: linkTitle} = cur;
-
-    if (cur.toHome) {
-      linkTitle ??= wikiInfo.nameShort;
-    } else if (cur.toCurrentPage) {
-      linkTitle ??= title;
-    }
-
-    let partContent;
-
-    if (typeof cur.html === 'string') {
-      partContent = cur.html;
-    } else {
-      const attributes = {
-        class: (cur.toCurrentPage || i === links.length - 1) && 'current',
-        href: cur.toCurrentPage
-          ? ''
-          : cur.toHome
-          ? to('localized.home')
-          : cur.path
-          ? to(...cur.path)
-          : null,
-      };
-      if (attributes.href === null) {
-        throw new Error(
-          `Expected some href specifier for link to ${linkTitle} (${JSON.stringify(
-            cur
-          )})`
-        );
-      }
-      partContent = html.tag('a', attributes, linkTitle);
-    }
-
-    if (!partContent) continue;
-
-    const part = html.tag('span',
-      {class: cur.divider === false && 'no-divider'},
-      partContent);
-
-    navLinkParts.push(part);
-  }
-
-  const navHTML = html.tag('nav',
-    {
-      [html.onlyIfContent]: true,
-      id: 'header',
-      class: [
-        ...nav.classes,
-        links.length && 'nav-has-main-links',
-        nav.content && 'nav-has-content',
-        nav.bottomRowContent && 'nav-has-bottom-row',
-      ],
-    },
-    [
-      links.length &&
-        html.tag(
-          'div',
-          {class: ['nav-main-links', ...nav.linkContainerClasses]},
-          navLinkParts
-        ),
-      nav.bottomRowContent &&
-        html.tag('div', {class: 'nav-bottom-row'}, nav.bottomRowContent),
-      nav.content && html.tag('div', {class: 'nav-content'}, nav.content),
-    ]);
-
-  const secondaryNavHTML = html.tag('nav',
-    {
-      [html.onlyIfContent]: true,
-      id: 'secondary-nav',
-      class: secondaryNav.classes,
-    },
-    secondaryNav.content);
-
-  const bannerSrc = banner.src
-    ? banner.src
-    : banner.path
-    ? to(...banner.path)
-    : null;
-
-  const bannerHTML =
-    banner.position &&
-    bannerSrc &&
-    html.tag('div',
-      {
-        id: 'banner',
-        class: banner.classes,
-      },
-      html.tag('img', {
-        src: bannerSrc,
-        alt: banner.alt,
-        width: banner.dimensions[0] || 1100,
-        height: banner.dimensions[1] || 200,
-      }));
-
-  const processSkippers = skipperList =>
-    skipperList
-      .filter(Boolean)
-      .map(([href, stringSubkey]) =>
-        html.tag('span', {class: 'skipper'},
-          html.tag('a',
-            {href},
-            language.$(`misc.skippers.${stringSubkey}`))));
-
-  // Hilariously jank. Sorry!
-  const hasID = id => mainHTML.includes(`id="${id}"`);
-  const hasTracks = hasID('tracks');
-  const hasArt = hasID('art');
-  const hasFlashes = hasID('flashes');
-  const hasContributors = hasID('contributors');
-  const hasReferences = hasID('references');
-  const hasReferencedBy = hasID('referenced-by');
-  const hasSamples = hasID('samples');
-  const hasSampledBy = hasID('sampled-by');
-  const hasFeatures = hasID('features');
-  const hasFeaturedIn = hasID('featured-in');
-  const hasLyrics = hasID('lyrics');
-  const hasSheetMusicFiles = hasID('sheet-music-files');
-  const hasMidiProjectFiles = hasID('midi-project-files');
-  const hasAdditionalFiles = hasID('additional-files');
-  const hasCommentary = hasID('commentary');
-  const hasArtistCommentary = hasID('artist-commentary');
-
-  const skippersHTML =
-    mainHTML &&
-      html.tag('div', {id: 'skippers'}, [
-        html.tag('span', language.$('misc.skippers.skipTo')),
-        html.tag('div', {class: 'skipper-list'},
-          processSkippers([
-            ['#content', 'content'],
-            sidebarLeftHTML &&
-              [
-                '#sidebar-left',
-                sidebarRightHTML
-                  ? 'sidebar.left'
-                  : 'sidebar',
-              ],
-            sidebarRightHTML &&
-              [
-                '#sidebar-right',
-                sidebarLeftHTML
-                  ? 'sidebar.right'
-                  : 'sidebar',
-              ],
-            navHTML &&
-              ['#header', 'header'],
-            footerHTML &&
-              ['#footer', 'footer'],
-          ])),
-
-        html.tag('div',
-          {
-            [html.onlyIfContent]: true,
-            class: 'skipper-list'
-          },
-          processSkippers([
-            hasTracks &&
-              ['#tracks', 'tracks'],
-            hasArt &&
-              ['#art', 'art'],
-            hasFlashes &&
-              ['#flashes', 'flashes'],
-            hasContributors &&
-              ['#contributors', 'contributors'],
-            hasReferences &&
-              ['#references', 'references'],
-            hasReferencedBy &&
-              ['#referenced-by', 'referencedBy'],
-            hasSamples &&
-              ['#samples', 'samples'],
-            hasSampledBy &&
-              ['#sampled-by', 'sampledBy'],
-            hasFeatures &&
-              ['#features', 'features'],
-            hasFeaturedIn &&
-              ['#featured-in', 'featuredIn'],
-            hasLyrics &&
-              ['#lyrics', 'lyrics'],
-            hasSheetMusicFiles &&
-              ['#sheet-music-files', 'sheetMusicFiles'],
-            hasMidiProjectFiles &&
-              ['#midi-project-files', 'midiProjectFiles'],
-            hasAdditionalFiles &&
-              ['#additional-files', 'additionalFiles'],
-            hasCommentary &&
-              ['#commentary', 'commentary'],
-            hasArtistCommentary &&
-              ['#artist-commentary', 'artistCommentary'],
-          ])),
-      ]);
-
-  const infoCardHTML = html.tag('div', {id: 'info-card-container'},
-    html.tag('div', {id: 'info-card-decor'},
-      html.tag('div', {id: 'info-card'}, [
-        html.tag('div', {class: ['info-card-art-container', 'no-reveal']},
-          img({
-            class: 'info-card-art',
-            src: '',
-            link: true,
-            square: true,
-          })),
-        html.tag('div', {class: ['info-card-art-container', 'reveal']},
-          img({
-            class: 'info-card-art',
-            src: '',
-            link: true,
-            square: true,
-            reveal: getRevealStringFromContentWarningMessage(
-              html.tag('span', {class: 'info-card-art-warnings'}),
-              {html, language}),
-          })),
-        html.tag('h1', {class: 'info-card-name'},
-          html.tag('a')),
-        html.tag('p', {class: 'info-card-album'},
-          language.$('releaseInfo.from', {
-            album: html.tag('a'),
-          })),
-        html.tag('p', {class: 'info-card-artists'},
-          language.$('releaseInfo.by', {
-            artists: html.tag('span'),
-          })),
-        html.tag('p', {class: 'info-card-cover-artists'},
-          language.$('releaseInfo.coverArtBy', {
-            artists: html.tag('span'),
-          })),
-      ])));
-
-  const imageOverlayHTML = html.tag('div', {id: 'image-overlay-container'},
-    html.tag('div', {id: 'image-overlay-content-container'}, [
-      html.tag('a', {id: 'image-overlay-image-container'}, [
-        html.tag('img', {id: 'image-overlay-image'}),
-        html.tag('img', {id: 'image-overlay-image-thumb'}),
-      ]),
-      html.tag('div', {id: 'image-overlay-action-container'}, [
-        html.tag('div', {id: 'image-overlay-action-content-without-size'},
-          language.$('releaseInfo.viewOriginalFile', {
-            link: html.tag('a', {class: 'image-overlay-view-original'},
-              language.$('releaseInfo.viewOriginalFile.link')),
-          })),
-
-        html.tag('div', {id: 'image-overlay-action-content-with-size'}, [
-          language.$('releaseInfo.viewOriginalFile.withSize', {
-            link: html.tag('a', {class: 'image-overlay-view-original'},
-              language.$('releaseInfo.viewOriginalFile.link')),
-            size: html.tag('span',
-              {[html.joinChildren]: ''},
-              [
-                html.tag('span', {id: 'image-overlay-file-size-kilobytes'},
-                  language.$('count.fileSize.kilobytes', {
-                    kilobytes: html.tag('span', {class: 'image-overlay-file-size-count'}),
-                  })),
-                html.tag('span', {id: 'image-overlay-file-size-megabytes'},
-                  language.$('count.fileSize.megabytes', {
-                    megabytes: html.tag('span', {class: 'image-overlay-file-size-count'}),
-                  })),
-              ]),
-          }),
-
-          html.tag('span', {id: 'image-overlay-file-size-warning'},
-            language.$('releaseInfo.viewOriginalFile.sizeWarning')),
-        ]),
-      ]),
-    ]));
-
-  const socialEmbedHTML = [
-    socialEmbed.title &&
-      html.tag('meta', {property: 'og:title', content: socialEmbed.title}),
-
-    socialEmbed.description &&
-      html.tag('meta', {
-        property: 'og:description',
-        content: socialEmbed.description,
-      }),
-
-    socialEmbed.image &&
-      html.tag('meta', {property: 'og:image', content: socialEmbed.image}),
-
-    ...html.fragment(
-      colors && [
-        html.tag('meta', {
-          name: 'theme-color',
-          content: colors.dark,
-          media: '(prefers-color-scheme: dark)',
-        }),
-
-        html.tag('meta', {
-          name: 'theme-color',
-          content: colors.light,
-          media: '(prefers-color-scheme: light)',
-        }),
-
-        html.tag('meta', {
-          name: 'theme-color',
-          content: colors.primary,
-        }),
-      ]),
-
-    oEmbedJSONHref &&
-      html.tag('link', {
-        type: 'application/json+oembed',
-        href: oEmbedJSONHref,
-      }),
-  ].filter(Boolean).join('\n');
-
-  return `<!DOCTYPE html>\n`
-}
-
-export function generateOEmbedJSON(pageInfo, {language, wikiData}) {
-  const {socialEmbed} = pageInfo;
-  const {wikiInfo} = wikiData;
-  const {canonicalBase, nameShort} = wikiInfo;
-
-  if (!socialEmbed) return '';
-
-  const entries = [
-    socialEmbed.heading && [
-      'author_name',
-      language.$('misc.socialEmbed.heading', {
-        wikiName: nameShort,
-        heading: socialEmbed.heading,
-      }),
-    ],
-    socialEmbed.headingLink &&
-      canonicalBase && [
-        'author_url',
-        canonicalBase.replace(/\/$/, '') +
-          '/' +
-          socialEmbed.headingLink.replace(/^\//, ''),
-      ],
-  ].filter(Boolean);
-
-  if (!entries.length) return '';
-
-  return JSON.stringify(Object.fromEntries(entries));
-}
-
-export function generateRedirectHTML(title, target, {
-  language,
-}) {
-  return `<!DOCTYPE html>\n` + html.tag('html', [
-    html.tag('head', [
-      html.tag('title', language.$('redirectPage.title', {title})),
-      html.tag('meta', {charset: 'utf-8'}),
-
-      html.tag('meta', {
-        'http-equiv': 'refresh',
-        content: `0;url=${target}`,
-      }),
-
-      // TODO: Is this OK for localized pages?
-      html.tag('link', {
-        rel: 'canonical',
-        href: target,
-      }),
-    ]),
-
-    html.tag('body',
-      html.tag('main', [
-        html.tag('h1',
-          language.$('redirectPage.title', {title})),
-        html.tag('p',
-          language.$('redirectPage.infoLine', {
-            target: html.tag('a', {href: target}, target),
-          })),
-      ])),
-  ]);
-}
-
-export function generateGlobalWikiDataJSON({
-  serializeThings,
-  wikiData,
-}) {
-  return '{\n' +
-    ([
-      `"albumData": ${stringifyThings(wikiData.albumData)},`,
-      wikiData.wikiInfo.enableFlashesAndGames &&
-        `"flashData": ${stringifyThings(wikiData.flashData)},`,
-      `"artistData": ${stringifyThings(wikiData.artistData)}`,
-    ]
-      .filter(Boolean)
-      .map(line => '  ' + line)
-      .join('\n')) +
-    '\n}';
-
-  function stringifyThings(thingData) {
-    return JSON.stringify(serializeThings(thingData));
-  }
-}
diff --git a/src/write/validate-writes.js b/src/write/validate-writes.js
deleted file mode 100644
index 52c7dfab..00000000
--- a/src/write/validate-writes.js
+++ /dev/null
@@ -1,136 +0,0 @@
-// TODO: All this is for an outdated spec + should use aggregate errors
-
-import {logError} from '../util/cli.js';
-
-function validateWritePath(path, urlGroup) {
-  if (!Array.isArray(path)) {
-    return {error: `Expected array, got ${path}`};
-  }
-
-  const {paths} = urlGroup;
-
-  const definedKeys = Object.keys(paths);
-  const specifiedKey = path[0];
-
-  if (!definedKeys.includes(specifiedKey)) {
-    return {error: `Specified key ${specifiedKey} isn't defined`};
-  }
-
-  const expectedArgs = paths[specifiedKey].match(/<>/g)?.length ?? 0;
-  const specifiedArgs = path.length - 1;
-
-  if (specifiedArgs !== expectedArgs) {
-    return {
-      error: `Expected ${expectedArgs} arguments, got ${specifiedArgs}`,
-    };
-  }
-
-  return {success: true};
-}
-
-function validateWriteObject(obj, {
-  urlSpec,
-}) {
-  if (typeof obj !== 'object') {
-    return {error: `Expected object, got ${typeof obj}`};
-  }
-
-  if (typeof obj.type !== 'string') {
-    return {error: `Expected type to be string, got ${obj.type}`};
-  }
-
-  switch (obj.type) {
-    case 'legacy': {
-      if (typeof obj.write !== 'function') {
-        return {error: `Expected write to be string, got ${obj.write}`};
-      }
-
-      break;
-    }
-
-    case 'page': {
-      const path = validateWritePath(obj.path, urlSpec.localized);
-      if (path.error) {
-        return {error: `Path validation failed: ${path.error}`};
-      }
-
-      if (typeof obj.page !== 'function') {
-        return {error: `Expected page to be function, got ${obj.content}`};
-      }
-
-      break;
-    }
-
-    case 'data': {
-      const path = validateWritePath(obj.path, urlSpec.data);
-      if (path.error) {
-        return {error: `Path validation failed: ${path.error}`};
-      }
-
-      if (typeof obj.data !== 'function') {
-        return {error: `Expected data to be function, got ${obj.data}`};
-      }
-
-      break;
-    }
-
-    case 'redirect': {
-      const fromPath = validateWritePath(obj.fromPath, urlSpec.localized);
-      if (fromPath.error) {
-        return {
-          error: `Path (fromPath) validation failed: ${fromPath.error}`,
-        };
-      }
-
-      const toPath = validateWritePath(obj.toPath, urlSpec.localized);
-      if (toPath.error) {
-        return {error: `Path (toPath) validation failed: ${toPath.error}`};
-      }
-
-      if (typeof obj.title !== 'function') {
-        return {error: `Expected title to be function, got ${obj.title}`};
-      }
-
-      break;
-    }
-
-    default: {
-      return {error: `Unknown type: ${obj.type}`};
-    }
-  }
-
-  return {success: true};
-}
-
-export function validateWrites(writes, {
-  functionName,
-  urlSpec,
-}) {
-  // Do a quick valid8tion! If one of the writeThingPages functions go
-  // wrong, this will stall out early and tell us which did.
-
-  if (!Array.isArray(writes)) {
-    logError`${functionName} didn't return an array!`;
-    return false;
-  }
-
-  if (!(
-    writes.every((obj) => typeof obj === 'object') &&
-    writes.every((obj) => {
-      const result = validateWriteObject(obj, {
-        urlSpec,
-      });
-      if (result.error) {
-        logError`Validating write object failed: ${result.error}`;
-        return false;
-      } else {
-        return true;
-      }
-    })
-  )) {
-    logError`${functionName} returned invalid entries!`;
-    return false;
-  }
-
-  return true;
-}