Abdulkader Safi

The example app stopped using the blocks the package ships

The last request before tagging v0.5.0 was that someone opening the example folder should understand everything without reading the package. That meant the demo site defining all nine of its own sections, which sounded like duplication and turned out to be the most honest thing in the repository.

4 min read

Share
A file tree of the example application showing nine block classes beside nine matching Blade views.

Atelier ships nine blocks: hero, features, rich text, image, gallery, logo wall, testimonials, FAQ, call to action. Register them with one line and a client can build a marketing site.

The example app used them, plus two of its own. Then came a request I initially read as duplication:

Instead of using the default blocks I want you to create custom blocks, because I want when someone opens the example folder he can understand everything, not needing to search for example blocks in the code of the package.

Why it is right

The example app is the only place a person can read a complete working site. Every time it borrows something from the package, understanding the site means opening two repositories in a mental split screen: this section is defined over there, its view is over there, but its data is here.

Worse, it made the example look like a consumer of a black box, when the entire promise of the package is that a block is one PHP class and one Blade view that you write. Demonstrating that by not writing any is a strange way to make the argument.

So the panel now registers exactly one list:

AtelierPlugin::make()
    ->blocks(SiteBlocks::all())

Nine classes in app/Blocks, nine views in resources/views/blocks, and nothing else decides what a page can hold. DefaultBlocks::all() is not called at all.

What that costs

Roughly 700 lines, most of it Blade. Every one of them is a line somebody can read without leaving the folder, and several of them are better than what the package ships because they are written for one brand rather than for everybody.

The hero class carries the fullest comments, because it is the one to read first: what type(), translatable(), defaults(), view() and schema() each decide, and why ->live(debounce: 400) is the difference between a preview that moves as you type and one that waits for you to click away.

Two things it broke

Four tests wanted an image block. The example's suite doubles as the package's, and the image upload tests add a block of type image, which the demo site does not have. They now register the package's block themselves:

// The panel registers this app's own blocks, and this site has no image
// block. These four tests cover the package's upload path, so they
// register the package's block to have something to upload into.
app(BlockRegistry::class)->register(ImageBlock::class);

Three lines of comment for one line of code, which is the correct ratio when the line exists because of a decision made somewhere else.

A picker test asserted a block that no longer exists. It checked that a page type narrows the section picker by looking for logo-wall being absent. There is no logo wall any more. The assertion now uses a block the site actually has, which is a better test anyway: it proves the narrowing with the site's own vocabulary.

What I would tell someone copying this

Do not do it in a client project. Start from DefaultBlocks::all() and add your own alongside, because nine free blocks are nine you do not maintain. The example is the one place where readability beats reuse, because being read is its entire job.

The SiteBlocks docblock says exactly that, so nobody takes the wrong lesson:

Deliberately not DefaultBlocks::all(). The package ships an equivalent set, and a real project usually starts from it, but this app defines its own so that everything on the site can be read in one folder without opening the package.

Where it stands

Shipped alongside v0.5.0. example/DEMO.md is a tour with a table of which file does what, a five-step trail through the panel and the public site, and the list of things the demo is deliberately demonstrating.

209 tests pass, the site runs, and the folder answers its own questions. A demo that needs a guided tour of another repository was not a demo, it was a screenshot with extra steps.

Last updated 13 Sep 2026 · filed under Laravel, blade, filamentphp, plugin, tools, web application, ui

Every entry on this project

28 build notes, in order

Including the ones where nothing worked. You are on part 24.

Read the series →

FAQ

Frequently asked questions

Should an example application reuse a library's defaults or define its own?

Define its own when the example exists to teach, reuse the defaults when it exists to prove integration. A reader working through a sample project should not have to open the library to find out what a component does, and writing the components in the sample is the clearest possible demonstration that writing them is easy. Say so in a comment though, because the same code copied into a real project would be throwing away maintained defaults for no reason.

What happens to a test suite when the sample application stops using the library's defaults?

Any test that relied on a default being registered starts failing, which is useful information: it tells you which parts of the library were only covered incidentally. Register the specific default inside the tests that cover it, rather than restoring the whole set globally, so each test states its own dependency. Where a test only used a default as a convenient fixture, rewrite it against something the sample actually has, which usually makes it a more honest test.

How much duplication is acceptable in the name of readability?

More than in production code, and only in artefacts whose job is to be read. Documentation, examples and teaching material are consumed linearly by someone who cannot jump to a definition, so a repeated implementation in front of them beats a reference to one elsewhere. The rule flips the moment the code has to be maintained under changing requirements, which is why the same duplication in a client project would be a mistake. Write the reason in the file, so the next person can tell which case they are in.

What makes a demo application actually useful to a newcomer?

Being complete and being local. Complete means a real site rather than a gallery of components: navigation, a brand, forms, an admin screen and content that reads like a client wrote it. Local means every file needed to understand it lives in the folder. Add a short guide mapping files to responsibilities and one walkthrough that crosses the whole system, because the value of a demo is showing how parts connect, which is exactly what per-feature documentation cannot do.

Written by

Abdulkader Safi

Software Engineer

Lead engineer at DSRPT, from Lebanon and based in Kuwait. I write about the tools and bugs from real client work, with the numbers I measured.

About me → GitHub LinkedIn

Need this kind of work done on your project?

Start a project →

Keep reading

More from Filament Atelier

All 28 entries →
  • A version number changing from 0.5.0 to 1.0.0 beside a list of deferred items.
    Filament Atelier

    · part 28 of 28

    Tagging 1.0.0 with four features missing, on purpose

    The gate list had thirteen items. Six went in, seven did not, and the tag went out anyway. What the number promises is that the API stops moving, not that the feature list is finished, and conflating those two is how packages sit at 0.x for three years.

  • The same block of JavaScript appearing in three different Blade layout files.
    Filament Atelier

    · part 26 of 28

    A script in three layouts is a contract nobody signed

    The editor's preview needed a few lines of JavaScript in the page it renders. I put them in the shipped layout, then copied them into two more. Anyone writing their own layout had to copy them too, and missing them broke half the editor with no error at all.