-
Notifications
You must be signed in to change notification settings - Fork 0
Challenges during dev
Backend architecture (Logger, EventRepository, schema migrations, REST API) plus the first React-powered admin screen. Documenting the real bugs hit today — the useful part of a dev log isn't "what I built," it's "what broke and why."
Symptom: table created fine on activation, but adding a new column later didn't trigger an ALTER TABLE — looked like the whole upgrade mechanism was broken.
Root cause: missing space in CREATE TABLE {$table}( — dbDelta() parses the table name with a regex (|CREATE TABLE ([^ ]*)|) that stops at the first space. No space before ( meant the regex consumed straight through into the column definitions, so dbDelta()'s internal "does this table already exist" check never matched a real table name.
Fix: CREATE TABLE {$table} ( — one space, non-negotiable formatting rule for dbDelta().
Lesson: dbDelta() is not a real SQL parser. Its formatting rules (two spaces before PRIMARY KEY's column list, KEY not INDEX, this space) are regex-driven quirks, not style preferences — get one wrong and it fails silently, not loudly.
Symptom: none yet, caught in review — $data had 7 keys, $format had 8 entries.
Root cause: copy-paste drift between the two arrays as fields were added.
Fix: define columns + format specifiers as one const array, derive both $data's expected keys and $format from the same source, so there's exactly one place to update per schema change.
Lesson: anything with two parallel arrays that must stay positionally in sync is a bug waiting to happen. Single source of truth beats "remember to update both."
Symptom: no bug yet, but a real design flaw — user_id column meant "who did this" in some events and "who it happened to" in others, with the real actor only ever present inside a free-text message string.
Root cause: didn't separate "who performed the action" from "what was acted on" as two distinct concepts up front.
Fix (in progress): consistently pass get_current_user_id() for the actor slot across every handler — except wp_login/wp_logout, where the hook fires after the session state already changed, so get_current_user_id() is unreliable there and the hook's own $user/$user_id parameter is the correct source of truth instead.
Lesson: "who did it" and "what it happened to" need to be separate, independently queryable columns from day one in an audit log. Retrofitting this is a migration; designing it in from the start is free. Also: know exactly when each hook fires relative to state changes — get_current_user_id() isn't a safe default everywhere.
Symptom: dependencies block listed ~400 packages (Playwright, Lighthouse, ESLint, ~everything @wordpress/scripts pulls in transitively) instead of the 3 packages actually imported by the code.
Root cause: looks like package-lock.json's flattened resolution tree got pasted into package.json by mistake.
Fix: rm -rf node_modules package-lock.json && npm install after trimming package.json back down to what's actually imported (@wordpress/element, @wordpress/api-fetch, @wordpress/components in dependencies; @wordpress/scripts in devDependencies).
Lesson: the test for "does this belong in dependencies" is simple — does any of my source code have an import/require for it? If not, it's either a transitive dependency (belongs in the lockfile only) or a dev-tool dependency (belongs in devDependencies).
Symptom: Invalid configuration object... has an unknown property 'theme'.
Root cause: tailwind.config.js's content (which legitimately has a theme key) ended up merged into webpack.config.js (which has no such key) — easy mistake with two similarly-named config files sitting at the same root level.
Fix: keep them strictly separate — webpack.config.js only ever touches entry/output/webpack-specific keys; tailwind.config.js only ever touches content/theme/corePlugins/important.
Lesson: when introducing a new tool that also wants a root-level *.config.js, double check which file is open before pasting.
Symptom: blank admin page, no console errors obviously pointing at the cause.
Root cause: custom entry: { admin: ... } in webpack.config.js outputs admin.js/admin.asset.php directly into build/, not into a build/admin/ subdirectory — PHP was looking one level too deep.
Fix: matched the require/enqueue paths to the actual webpack output filenames.
Lesson: after any webpack config change, run ls -la on the actual build output before wiring up the PHP side — confirm the real filenames rather than assuming them from the entry key name.
Root cause: find . -name "*.php" walks vendor/, and some packages (PHPStan, PHPCS) ship deliberately-malformed PHP files used to test their own parsers — php -l against those fails CI for a reason unrelated to my code.
Fix: find . -path ./vendor -prune -o -path ./node_modules -prune -o -name "*.php" -print0 | xargs -0 -n1 php -l.
Lesson: exclude vendor/node_modules from any blanket file-walking step in CI, not just the obvious ones (linting, tests) — anything that globs **/*.php needs this guard.
A lot of today's bugs share one root shape: two things that need to stay in sync, kept in sync by memory instead of by structure — $data/$format arrays, package.json/actual imports, webpack.config.js/tailwind.config.js, build output path/enqueue path. The fix pattern is almost always the same: derive one from the other, or make the mismatch impossible to miss (a guard, a single source of truth) rather than trusting future-me to remember.
-
permission_callbackon/logsREST route still__return_true— needscurrent_user_can()gating before this goes further - Unit tests for
Logger(mockedEventRepository) andEventRepository::query()(realWP_UnitTestCase) - RBAC — capability checks, likely a custom capability rather than reusing
manage_optionsdirectly - Admin UI: filters (event type, actor, date range), sorting, search, cursor pagination wired to the
id-based cursor already inEventRepository::query() - Gutenberg dynamic block + editor SlotFill — not started yet