Skip to content
View in the app

A better way to browse. Learn more.

ernestdefoe.online

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
ernestdefoe.online

Extensions, themes & support for Flarum and Invision Community

Vibe coding for the community web. Report a bug, request a feature, or dig into the source — this is where the tools you use get built, in the open.

We do custom Bespoke Invision Community apps. If you have an idea for something you want then use the contact form to get in touch with us.
Knowledge base

Things that cost me a day, so they cost you none

Working notes from building Invision Community and Flarum applications. Mostly the failures that give no error at all — the ones where everything installs cleanly and quietly does the wrong thing.

92 articles

Invision Community 5

86 articles

Extensions and contracts

39

What each extension point is for, what it must declare, and what happens when it is wrong — which is usually nothing visible.

Languages and text

5

The string table, translation, and the places where text does not appear where you expected it to.

Theming, templates and forms

9

Theme hooks, CSS that survives both colour schemes, and building forms that do not throw on render.

Background work and scheduled tasks

5

The queue system, work that has to happen after the response, and jobs that finish without doing anything.

Data, settings and storage

11

The database layer, settings, tags, file storage, and backing up a live site.

AI features and expectations

5

What these features do, what they cost, and what buyers reasonably but wrongly assume they do.

Application structure and releases

11

The JSON files an application is made of, versioning and upgrade steps, and testing from the command line.

Realtime, chat and calls

1

WebSocket gateways, relays and the server-side pieces live features depend on — where "it works when I test it" and "it works for your members" are different claims.

Nothing matches that.

Data, settings and storage

The database layer, settings, tags, file storage, and backing up a live site.

11 Articles in this category

  1. Ernest Defoe ·
    If you are adding podcast episodes to a community, the temptation is a Pages database with an audio upload field and a player on the template. That gets you a web page. It does not get you a podcast, because a podcast is the RSS feed, and directories are unforgiving about details that produce no error anywhere. These are the rules worth knowing before you write the feed, each one a documented rejection or a player misbehaving. The namespaces, spelled exactly <rss version="2.0" xmln
    • 0 comments
    • 23 views
  2. Ernest Defoe ·
    Create a Pages category from code, move some records into it, and every one of those records becomes unreachable. The browser reports too many redirects; the server is issuing a 301 to the same URL over and over. The category itself is fine — it lists correctly, the AdminCP shows nothing unusual, and the records are present and approved. The cause is one column that nothing asked you to fill in: category_full_path. Why it loops rather than 404s Pages builds a record's canonical URL from th
    • 0 comments
    • 23 views
  3. Ernest Defoe ·
    Two things about customising a Pages "html" page that each cost an hour, and both fail in a way that looks like something else. 1. page_content is not run through the template engine A theme's custom header is compiled — Theme::getHeaderAndFooter() calls Theme::compileTemplate(), so you can write {{if}}, {{foreach}} and {expression="..."} in it and they execute. A Pages page's page_content is not. Pages parses its own tags — {database="kb"}, {block="..."}, {media="..."} — and emits everythin
    • 0 comments
    • 87 views
  4. Ernest Defoe ·
    Writing a database dump in pure PHP is not hard. Writing one that actually restores is, because every way it fails is silent. These are the ones that cost real time on Invision Community specifically. Why pure PHP at all Invision Community's own Configuration Error notification lists exec, system, passthru, pcntl_exec, popen, proc_open and shell_exec as dangerous and tells administrators to disable them. Take that advice — as you should — and mysqldump becomes unreachable. Any backup approach
    • 0 comments
    • 34 views
  5. Ernest Defoe ·
    Invision Community 5 fires 58 event-listener hooks. None of them concern tags. Tags do not go through a model with a save() you can observe. They arrive through Content\Taggable::setTags(), which deletes the item's existing rows in core_tags and inserts the new ones directly. No event, no queue job, no notification. So there is no supported way to be told that a tag has come into existence. The trap this sets Any application whose state is derived from the tag list has to refresh that state
    • 0 comments
    • 40 views
  6. Ernest Defoe ·
    A core/FileStorage extension is how an application tells the suite that it owns a bucket of files. Each extension becomes one row on AdminCP → System → Files → Storage Settings, one entry in the upload_settings setting, and one unit of work for the MoveFiles background task. Core calls it in exactly four situations: when the admin repoints that bucket at a different storage configuration (count() then repeated move()), when the admin runs Find orphaned files (isValidFile() for every file in a co
    • 0 comments
    • 39 views
  7. Ernest Defoe ·
    Reading settings with ?: is idiomatic and, for anything numeric, wrong: $perPage = Settings::i()->myapp_per_page ?: 25; PHP treats 0, '0' and '' as falsy. If an administrator sets that field to 0 — a perfectly reasonable way to say "none" or "unlimited" — they get 25 instead. The setting appears to do nothing, and no amount of re-saving it helps. Where this actually bites Anything meaning "unlimited", where 0 is the natural way to express it. Anything meaning "off", where 0 disables a f
    • 0 comments
    • 24 views
  8. Ernest Defoe ·
    Two boolean flags in a row, easy to transpose, and they mean opposite things. Getting them the wrong way round produces data corruption rather than an error. The signature public function insert( string $table, Select|array $set, bool $odkUpdate = FALSE, // 3rd: ON DUPLICATE KEY UPDATE bool $ignoreErrors = FALSE // 4th: INSERT IGNORE ): int|string|mysqli_stmt What each does CallOn a duplicate key insert( $t, $v )Throws insert( $t, $v, TRUE )Overwrites the existing r
    • 0 comments
    • 26 views
  9. Ernest Defoe ·
    The supported entry point is Databases::createDatabase(), which builds the records table, the default title and content fields, and a default category. $db = new \IPS\cms\Databases; $db->key = 'kb'; $db->use_categories = 1; $db->save(); // createDatabase() READS these while building the fields, so write them first \IPS\Lang::saveCustom( 'cms', 'content_db_' . $db->id, 'Knowledge Base' ); \IPS\Lang::saveCustom( 'cms', 'content_db_lang_pu_' . $db->id, 'articles' ); \IP
    • 0 comments
    • 27 views
  10. Ernest Defoe ·
    Anything run from the command line that touches S3-compatible storage fails with BAD_XML. That includes cron tasks and any maintenance script. The symptom RuntimeException: BAD_XML #0 /system/File/Amazon.php(823): IPS\Http\Response->decodeXml() #1 /system/File/Amazon.php(349): IPS\File\Amazon::makeRequest(..., 'put', ...) In the AdminCP it often shows up first as a Locked Task warning for s3Delete, with a climbing lock count that never clears. The cause IPS\File\Amazon::buildBaseUrl() pic
    • 0 comments
    • 29 views
  11. Ernest Defoe ·
    There is no "add a tag" operation in Invision Community. Anything that tags content programmatically must pass the complete final set, or it silently destroys work someone did by hand. What setTags() actually does Db::i()->delete( 'core_tags', array( 'tag_aai_lookup=?', $aaiLookup ) ); foreach ( $set as $key => $tag ) { try { Tag::load( $tag, 'tag_text' ); // must already exist // ... insert } catch( \OutOfRangeException ) {} // unknown tags are dro
    • 0 comments
    • 32 views

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.