Bliki Phase 4.1: Code Quality & Consolidation

After the WYSIWYG editor landed on July 4, the next week focused on three fronts: consolidation (making embed/ the single source of truth), component refactoring (property naming, event cleanup, state map), and e2e test infrastructure (fixtures, server lifecycle, dead code removal).

Consolidation: embed/ Is the Single Source of Truth

The project root once contained a theme/ directory alongside the embed/theme/ directory. This was a regression — theme/ at the root was never used by any command. new site and theme update both extract from embed/theme/ via //go:embed.

The 235a383 commit removed the entire theme/ directory at the project root:

theme/css/editor.css
theme/css/style.css
theme/js/bliki-content.js
theme/js/bliki-editable.js
theme/js/bliki-editor.js
theme/js/bliki-logged-user.js
theme/js/bliki-login.js
theme/js/bliki-preview.js
theme/js/bliki-toolbar.js
theme/templates/about.tmpl
theme/templates/admin-page.tmpl
theme/templates/blog.tmpl
theme/templates/default.tmpl
theme/templates/index.tmpl
theme/templates/login.tmpl
theme/templates/post.tmpl
# ... 25 files, 1172 lines removed

This was a significant cleanup — the root theme/ had grown alongside embed/theme/ during development, and the two were drifting apart. Consolidating to embed/ eliminated the ambiguity.

E2e test fixtures that previously symlinked to theme/js/ were updated to point at embed/theme/js/:

const embedJsLink = join(BLIKI_ROOT, 'embed', 'theme', 'js');
execSync(`ln -s "${embedJsLink}" "${jsLink}"`, { stdio: 'ignore' });

The copyAssets function was updated to follow directory symlinks when copying theme files to public/.

Component Refactoring

Several incremental improvements were made to the web components over July 6:

El Suffix on Element Properties

Element references were renamed from bare names to the El suffix convention — editoreditorEl, editBtneditBtnEl, etc. This matches the existing naming pattern used elsewhere in the codebase (e.g., previewContentpreviewContentEl).

The editorEl typo was also fixed — it had been referenced as editorEl in some places and editor in others, causing intermittent null references.

const → let

All const declarations in embed/theme/js/*.js were replaced with let. This was a style consistency pass — several variables were being reassigned in if/try blocks, making const declarations technically incorrect even though they were dead on the happy path.

setState: Switch Statement → Prototype Map

The most impactful refactoring was replacing the setState switch statement with a prototype-level state handler map:

Before:

setState(state, message) {
    this._state = state;
    this.setAttribute('data-state', state);

    switch (state) {
    case 'loading':
        this._showLoading(true);
        this._hideUI();
        break;
    case 'idle':
        this._showLoading(false);
        this._hideError();
        if (this.editBtnEl) this.editBtnEl.hidden = !this._canWrite;
        break;
    // ... 6 more cases
    }
}

After:

static {
    BlikiEditable.prototype._stateHandlers = {
        loading: function() {
            this._showLoading(true);
            this._hideUI();
        },
        idle: function() {
            this._showLoading(false);
            this._hideError();
            if (this.editBtnEl) this.editBtnEl.hidden = !this._canWrite;
        },
        // ... all state handlers
    };
}

setState(state, message) {
    this._state = state;
    this.setAttribute('data-state', state);
    this._stateHandlers[state]?.call(this, message);
}

The map lives on BlikiEditable.prototype — it's a single copy shared by all instances, not a separate object per instance. Functions are defined in a static {} block at class level so they're registered before any instances are created.

init() Bug Fix

init() was reading this.content (undefined, since there is no content property) instead of this._content (the private field set in connectedCallback()). The fix was simple but critical:

async init() {
    try {
        let content = this._content;
        if (!content) return;
        // ... rest of init
    }
}

Without this fix, the entire initialization chain was silently skipped — no auth check, no lock status, no rendered content fetch.

Unused Event Params

Event handler methods onEditStart and onEditCancel had unused event parameters. These were removed since the handlers didn't access event.detail — they operated purely on this state.

Preview Rendering Fix

A bug in bliki-preview.js prevented the initial content from showing when the editor opened. The issue was a property path mismatch:

BlikiEditor.onEditStart dispatched:

document.dispatchEvent(new CustomEvent('bliki-preview:edit-start', {
    detail: { textareaContent, content }
}));

But BlikiPreview.onEditStart accessed:

this._updatePreview(event.detail.data.textareaContent);

The detail.data path didn't exist — the data was at event.detail.textareaContent directly. The fix:

onEditStart(event) {
    this.removeAttribute('hidden');
    this._updatePreview(event.detail.textareaContent);
}

Bug Fixes

Flaky waitForResponse

The logged-user e2e test used page.waitForResponse() to wait for the /api/user response, but Playwright's response matching was unreliable — sometimes the response arrived before the waitForResponse listener was attached. The fix switched to waiting on DOM state instead:

// Before: flaky
await page.waitForResponse(/\/api\/user/);

// After: reliable
await page.getByText('admin').waitFor();

test-results/ to .gitignore

test-results/ (where Playwright stores screenshots, videos, and trace files) was added to .gitignore to prevent test artifacts from accumulating in the repository.

Git Repo Warning

When bliki build --serve runs without a .git/ directory, the log was showing an error. This was changed to a warning since git is optional:

// Before: log.Error("no git repo")
// After:  log.Warn("no git repo")

E2E Test Infrastructure

Fixture-Based Testing

The old Playwright suite (tests/playwright/) was replaced with a fixture-based approach. Each test suite has its own isolated fixture directory with:

tests/e2e/fixtures/<suite>/
  bliki.toml
  content/
  theme/
    js/       ← symlink to embed/theme/js/
    templates/

The globalSetup.js script creates symlinks from theme/js/embed/theme/js/ and starts a separate Bliki server for each fixture on its own port (3002–3006).

Server Lifecycle Management

The teardown function now ensures all servers are terminated after tests:

function killServers() {
    execSync('kill $(lsof -t -i:3002-3006) 2>/dev/null || true');
}

Redundant exit messages were removed from globalSetup.js — the test runner already reports pass/fail status.

Dead Git Setup Removal

The globalSetup.js fixture setup previously ran git init, git remote add, git add, git commit, and git push as part of preparing the editor test fixture. These were dead code — no active tests use /api/save or trigger git operations. The bliki.toml has no git_remote_url, so go-git isn't needed.

Removing this cleanup cut 27 lines of dead code and eliminated a failure point: when the git setup was commented out in an earlier experiment, the git push command failed, which killed the server startup and all e2e tests. The fix was to remove the entire workflow, since the fixture runs fine without a .git/ directory.

JSDoc

JSDoc comments were added to all public functions across the seven web component files:

The pattern: concise single-paragraph JSDoc describing what the function does and any event interactions. No parameter/type annotations — the code is simple enough that names are self-documenting.

What Was Cleaned Up

Change Files Lines
Remove root theme/ 25 files -1172
Dead git setup from globalSetup 1 file -27
Unused event params 1 file -4
test-results/ .gitignore 1 file +1

The project root went from ~1200 lines of dead theme files to zero.

Test Results

All 27 e2e tests pass (26 existing + 1 new: two-editors.spec.js). The new test verifies that two independent <bliki-editable> components on the same page manage their state independently — each respects its own content attribute and doesn't interfere with the other.

What's Next

Phase 4.1 consolidated the codebase and cleaned up the test infrastructure. The next phase targets wiki features: bidirectional linking, [[Page Name]] syntax, automatic page indexing, and search functionality.