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 — editor → editorEl, editBtn → editBtnEl, etc. This matches the existing naming pattern used elsewhere in the codebase (e.g., previewContent → previewContentEl).
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:
bliki-editable.js— class JSDoc,rawgetter/setter,setState,handleEventbliki-editor.js—rawgetter/setter,textareagetter,onEditStart,onEditCancel,onSaveClick,onCancelClickbliki-preview.js— class JSDoc,handleEventbliki-toolbar.js— class JSDoc,handleActionbliki-login.js—handleEvent,onsubmit,emitbliki-content.js—emitbliki-logged-user.js—emit
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.