Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9708330
add llms file
OStefan2001 Aug 20, 2026
f320907
redirect with 301 for wp- path
OStefan2001 Aug 20, 2026
a6750c2
test for redirect
OStefan2001 Aug 20, 2026
6298e79
Merge pull request #140 from dotkernel/add-llms
bidi47 Aug 20, 2026
8377e18
updated no-route redirect, error message design
bidi47 Aug 20, 2026
90a459f
updated includes
bidi47 Aug 20, 2026
ac12071
added opengraph for article
bidi47 Aug 20, 2026
429bf31
Merge pull request #145 from dotkernel/issue-139
OStefan2001 Aug 20, 2026
e132703
Merge pull request #144 from dotkernel/redirect
OStefan2001 Aug 20, 2026
d3caa65
updated the error pages
bidi47 Aug 20, 2026
757ed66
Merge pull request #146 from dotkernel/error-pages
OStefan2001 Aug 20, 2026
fd82876
enforced trailing slash
bidi47 Aug 20, 2026
cabb42e
updated footer
bidi47 Aug 20, 2026
e8ea53e
Merge pull request #150 from dotkernel/issue-138
arhimede Aug 20, 2026
0a7e4d2
Merge pull request #149 from dotkernel/trailing-slash
arhimede Aug 20, 2026
21d1604
updated footer layout
bidi47 Aug 21, 2026
d1cdec5
updated footer
bidi47 Aug 21, 2026
540dd12
Merge pull request #152 from dotkernel/issue-138
arhimede Aug 21, 2026
3c3695d
updated .gitignore for public/uploads
bidi47 Aug 21, 2026
208d7ff
Merge pull request #153 from dotkernel/issue-136
arhimede Aug 21, 2026
1ee9755
Merge pull request #154 from dotkernel/issue-138
arhimede Aug 21, 2026
72e45b9
updated footer on mobile
bidi47 Aug 21, 2026
6c6452e
updated footer
bidi47 Aug 21, 2026
dcb4b8d
Merge pull request #155 from dotkernel/issue-138
arhimede Aug 21, 2026
b4aa4c1
updated json-ld
bidi47 Aug 21, 2026
80d9d64
Merge pull request #156 from dotkernel/issue-151
arhimede Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ package-lock.json
# Ignore the file .DS_Store in all directories
**/.DS_Store

public/uploads/

# Generated by bin/generate-packages (daily cron); not tracked so it never conflicts on pull
/public/dotkernel-packages.json
/public/dotkernel-packages.json.tmp
Expand Down
16 changes: 5 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Add a new entry to the category's `articles` array in `src/App/src/Fixture/artic
| `"archived"` | `PostStatusEnum::Archived` | Not published: excluded from listings/feed/sitemap, but its own page returns `410 Gone` instead of `404` — use this for content that existed and was intentionally removed (outdated articles, leftover test content, etc.), as opposed to content that was never public. |
| anything else (including `"draft"`) | `PostStatusEnum::Draft` | Not published: excluded from listings/feed/sitemap, its own page returns `404`. This is also the fallback for typos in `post_status`. |

After changing `post_status`, follow the same steps: re-run `bin/doctrine-fixtures`, then `bin/generate-feed` and `bin/sitemap`. This applies generally, not just to status changes — **any** edit to `articles_cleaned.json` (title, excerpt, status, date, etc.) needs `bin/doctrine-fixtures` re-run to update the database, followed by re-running the 3 generators in step 4 so `feed.xml`/`sitemap.xml`/`llms-full.txt` reflect it. One exception: `bin/generate-llms-full` reads straight from the `.md` files on disk and does **not** check `post_status` at all — a non-published article's `.md` file will still be included in `llms-full.txt` unless you also remove or rename that file.
After changing `post_status`, follow the same steps: re-run `bin/doctrine-fixtures`, then `bin/generate-feed` and `bin/sitemap`. This applies generally, not just to status changes — **any** edit to `articles_cleaned.json` (title, excerpt, status, date, etc.) needs `bin/doctrine-fixtures` re-run to update the database, followed by re-running the 4 generators in step 4 so `feed.xml`/`sitemap.xml`/`llms.txt`/`llms-full.txt` reflect it. One exception: `bin/generate-llms-full` reads straight from the `.md` files on disk and does **not** check `post_status` at all — a non-published article's `.md` file will still be included in `llms-full.txt` unless you also remove or rename that file.

## 2. Create the templates

Expand All @@ -68,24 +68,18 @@ php bin/create-uploads-dir
```shell
php bin/generate-feed
php bin/sitemap
php bin/generate-llms
php bin/generate-llms-full
```

- `bin/generate-feed` rewrites `public/feed.xml` from the published posts in the database.
- `bin/sitemap` rewrites `public/sitemap.xml` from the published posts in the database.
- `bin/generate-llms` rewrites `public/llms.txt`, the short curated index (one line per article, grouped by category) — built from the published posts in the database, grouped by category (known categories first, in a fixed order, then any remaining categories by post count) and sorted alphabetically by title within each category. Requires the `llms.sourceDir` / `llms.indexFile` keys in `config/autoload/local.php` (see `local.php.dist`); the `llms.pagesDir` key is optional, same as for `bin/generate-llms-full` below.
- `bin/generate-llms-full` rewrites `public/llms-full.txt` by concatenating `public/md-articles/index.md` and every other `public/md-articles/*/*.md` file, sorted by path, then appending each `public/md-pages/*.md` file — the markdown versions of the static pages — labelled with a `md-pages/` prefix in the section header. Requires the `llms.sourceDir` / `llms.outputFile` keys in `config/autoload/local.php` (see `local.php.dist`); the `llms.pagesDir` key is optional, and omitting it leaves the page sections out.

These three have no ordering dependency on each other, only on step 3 being done first.
These four have no ordering dependency on each other, only on step 3 being done first.

**`public/llms.txt` is not part of this - it is edited by hand, not generated.** It's a separate, curated index (one line per article, grouped by category) distinct from the full-text `llms-full.txt`. Whenever an article is added, add a matching entry under its category:

```markdown
- [Your article title](https://www.dotkernel.com/{category-slug}/{article-slug}/): One-sentence description, similar to the excerpt.
```

Also bump that category's post count in its heading (e.g. `## Dotkernel (65 posts)`). Entries are ordered alphabetically by title within each category.

None of this is wired into an automated deploy pipeline in this repository - there is no `deploy` script or CI job that runs these `bin/` scripts. `public/feed.xml`, `public/sitemap.xml`, and `public/llms-full.txt` are committed generated artifacts, so re-running these scripts leaves them modified in git until committed.
None of this is wired into an automated deploy pipeline in this repository - there is no `deploy` script or CI job that runs these `bin/` scripts. **This must be run manually as part of every deploy** whenever `articles_cleaned.json` changed since the last deploy. `public/feed.xml`, `public/sitemap.xml`, `public/llms.txt`, and `public/llms-full.txt` are committed generated artifacts, so re-running these scripts leaves them modified in git until committed.

## How to update an article

Expand Down
5 changes: 5 additions & 0 deletions public/.htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]

RewriteCond %{REQUEST_METHOD} GET|HEAD
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{REQUEST_URI} !\.[^/]+$
RewriteRule ^(.*)$ /$1/ [R=301,L]

# The following rewrites all other queries to index.php. The
# condition ensures that if you are using Apache aliases to do
# mass virtual hosting, the base path will be prepended to
Expand Down
2 changes: 1 addition & 1 deletion public/css/app.css

Large diffs are not rendered by default.

Binary file added public/images/app/content/workers.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added public/llms.txt
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/opengraph/dotkernel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/App/assets/images/content/workers.jpg
91 changes: 89 additions & 2 deletions src/App/assets/scss/components/_custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ section {
padding: 88px 0;
}

.section-bg-light {
background:var(--surface-2);
border-top:1px solid var(--border-soft);
border-bottom:1px solid var(--border-soft);
}

.section-head {
max-width: 640px;
margin-bottom: 48px;
Expand Down Expand Up @@ -519,8 +525,8 @@ html[data-theme="light"] .theme-toggle {

p.sub {
font-size: 18px;
max-width: 547px;
margin-top: 20px;
max-width: 520px;
color: var(--text-muted);

strong {
Expand All @@ -537,6 +543,32 @@ html[data-theme="light"] .theme-toggle {
align-items: center;
}

/* ============================================================
Error pages
============================================================ */

h1.error-code {
text-align: center;
}

.hero h1.error-code {
font-size: 88px;
}

.page-intro h1.error-code {
font-size: 80px;
}

.error-sub,
.error-message {
text-align: center;
margin: auto;
}

.error-buttons {
justify-content: center;
}

.hero-ctas {
display: flex;
gap: 14px;
Expand Down Expand Up @@ -1263,7 +1295,6 @@ details.acc {
}

.about-band {
background: var(--surface);
border-top: 1px solid var(--border-soft);
border-bottom: 1px solid var(--border-soft);
}
Expand Down Expand Up @@ -1296,6 +1327,58 @@ details.acc {

footer {
padding: 50px 0 40px;
position: relative;
overflow: hidden;

&::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
background: url("/images/app/content/workers.jpg") center / cover no-repeat;
opacity: .06;
pointer-events: none;
}

.wrap {
position: relative;
z-index: 1;
}
}

.footer-logo {
margin-bottom: 40px;

@media (max-width: 992px) {
display: none;
}

.logo-full {
height: 52px;
}
}

.footer-announcement {
background: #fff;
border: 1px solid var(--border-soft);
border-left: 3px solid var(--teal);
border-radius: var(--radius);
padding: 32px 40px;
margin-bottom: 40px;

p {
margin-top: 10px;
font-size: 14px;
color: rgba(11, 11, 11, .65);
}
}

.footer-announcement-cta {
margin-top: 20px;

@media (min-width: 992px) {
margin-top: 0;
}
}

// Link columns mirroring the top menu, above the copyright/social row.
Expand Down Expand Up @@ -1460,6 +1543,10 @@ footer {
font-size: 32px;
}

.hero h1.error-code {
font-size: 64px;
}

.products-grid {
grid-template-columns: 1fr;
}
Expand Down
2 changes: 1 addition & 1 deletion src/App/src/Fixture/articles_cleaned.json
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,7 @@
"github": "OStefan2001"
},
"isObsolete": false,
"opengraph_img": null,
"opengraph_img": "/opengraph/article/twitter-card-build-dotkernel.png",
"excerpt": "How this blog itself came together: starting from the Dotkernel Light starter project, then following the Tutorial 101 to add Doctrine ORM and load real data into the database.",
"tl_dr": "This project started from the Dotkernel Light starter (Mezzio, Twig, FastRoute, PSR-7 via Laminas Diactoros) as a bare-bones website skeleton.\nFollowing the official Tutorial 101, Doctrine ORM was added on top: entities for posts, categories, authors and tags, migrations to create the schema, and fixtures to load real content into the database.\nThe result is this: a Dotkernel Light project turned into a fully data-driven site.",
"tags": [
Expand Down
11 changes: 0 additions & 11 deletions src/App/src/RoutesDelegator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Light\App;

use Laminas\Diactoros\Response\RedirectResponse;
use Light\App\Handler\GetFeedViewHandler;
use Light\App\Handler\GetIndexViewHandler;
use Light\App\Handler\GetMarkdownArticleHandler;
Expand Down Expand Up @@ -34,16 +33,6 @@ public function __invoke(ContainerInterface $container, string $serviceName, cal
GetPackagesViewHandler::TEMPLATE
);

$app->get('/{first}', function ($request) {
$uri = $request->getUri();
return new RedirectResponse((string) $uri . '/', 301);
});

$app->get('/{first}/{second}', function ($request) {
$uri = $request->getUri();
return new RedirectResponse((string) $uri . '/', 301);
});

return $app;
}
}
8 changes: 4 additions & 4 deletions src/App/templates/app/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
</div>
</section>

<section style="background:var(--surface-2);border-top:1px solid var(--border-soft);border-bottom:1px solid var(--border-soft);">
<section class="section-bg-light">
<div class="wrap">
<div class="section-head">
<span class="eyebrow">Headless Platform</span>
Expand Down Expand Up @@ -205,7 +205,7 @@
</div>
</section>

<section style="background:var(--surface-2);border-top:1px solid var(--border-soft);border-bottom:1px solid var(--border-soft);">
<section class="section-bg-light">
<div class="wrap">
<div class="section-head">
<span class="eyebrow">Other skeletons</span>
Expand Down Expand Up @@ -258,7 +258,7 @@
</div>
</section>

<section style="background:var(--surface-2);border-top:1px solid var(--border-soft);border-bottom:1px solid var(--border-soft);">
<section>
<div class="wrap">
<div class="section-head">
<span class="eyebrow">Components</span>
Expand Down Expand Up @@ -361,7 +361,7 @@
</div>
</section>
{% if posts is defined and posts is iterable %}
<section>
<section style="background:var(--surface-2);border-top:1px solid var(--border-soft);border-bottom:1px solid var(--border-soft);">
<div class="wrap">
<div class="section-head">
<span class="eyebrow">From the blog</span>
Expand Down
10 changes: 5 additions & 5 deletions src/App/templates/error/404.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
<section class="hero">
<div class="wrap">
<span class="eyebrow">Oops! This is awkward.</span>
<h1>404</h1>
<p class="sub">
You are looking for something that doesn't exist or may have moved.
Check out one of the categories below, or head back home.
<h1 class="error-code">404</h1>
<p class="sub error-sub">
You are looking for something that doesn't exist or may have moved.<br>
Return to the homepage below, or head to the Blog.
</p>

<div class="hero-ctas">
<div class="hero-ctas error-buttons">
<a class="btn btn-primary" href="{{ path('app::index') }}">Back to Home</a>
<a class="btn btn-ghost" href="{{ url('page::blog') }}">Browse new posts</a>
</div>
Expand Down
8 changes: 4 additions & 4 deletions src/App/templates/error/410.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
<section class="hero">
<div class="wrap">
<span class="eyebrow">Gone. This article was removed.</span>
<h1>410</h1>
<p class="sub">
The content was misleading, old and obsolete, so we removed it.
<h1 class="error-code">410</h1>
<p class="sub error-sub">
The content was misleading, old and obsolete, so we removed it.<br>
It is gone for good and won't be coming back.
</p>

<div class="hero-ctas">
<div class="hero-ctas error-buttons">
<a class="btn btn-primary" href="{{ path('app::index') }}">Back to Home</a>
<a class="btn btn-ghost" href="{{ url('page::blog') }}">Browse new posts</a>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/App/templates/error/error.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<div class="page-intro home-intro error-messages">
<div class="container">
<span class="eyebrow">Oops! This is awkward.</span>
<h1>{{ status }}</h1>
<h2 class="message">{{ reason }}</h2>
<h1 class="error-code">{{ status }}</h1>
<h2 class="message error-message">{{ reason }}</h2>
</div>
</div>
{% endblock %}
Loading