Skip to content

Commit 34a87a6

Browse files
authored
feat: monospace design (#104)
1 parent 50d3a91 commit 34a87a6

38 files changed

Lines changed: 380 additions & 223 deletions

src/Web/Blog/BlogPostTag.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ enum BlogPostTag: string
1313
public function getStyle(): string
1414
{
1515
return match ($this) {
16-
self::THOUGHTS => 'ring-amber-200 text-amber-400',
17-
self::RELEASE => 'ring-blue-200 text-blue-400',
18-
self::TUTORIAL => 'ring-teal-200 text-teal-400',
16+
self::THOUGHTS => 'bg-yellow-400/20 dark:bg-yellow-400/10 text-yellow-700 dark:text-yellow-400',
17+
self::RELEASE => 'bg-blue-400/20 dark:bg-blue-400/10 text-blue-700 dark:text-blue-400',
18+
self::TUTORIAL => 'bg-teal-400/20 dark:bg-teal-400/10 text-teal-700 dark:text-teal-400',
1919
};
2020
}
2121
}

src/Web/Blog/articles/2025-03-13-request-objects-in-tempest.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Request Objects in Tempest
2+
title: Request objects in Tempest
33
description: Why Tempest requests are super intuitive
44
author: brent
55
tag: Tutorial

src/Web/Blog/articles/2025-03-30-about-route-attributes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: About Route Attributes
2+
title: About route attributes
33
description: Let's explore Tempest's route attributes in depth
44
author: brent
55
tag: Thoughts

src/Web/Blog/articles/2025-05-08-beta-1.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Tempest is Beta
2+
title: Tempest is beta
33
description: |
44
Today we release the first beta version of Tempest, the PHP framework for web and console apps that gets out of your way. It's one of the final steps towards a stable 1.0 release. We'll use this beta phase to fix bugs, and we're committed to not making any breaking changes anymore, apart from experimental features.
55
author: brent

src/Web/Blog/articles/2025-05-26-tempests-vision.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Tempest's Vision
2+
title: Tempest's vision
33
description: What sets Tempest apart as a framework for modern PHP development.
44
author: brent
55
tag: Thoughts

src/Web/Blog/articles/2025-07-28-tempest-view-updates.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
---
2-
title: Major updates to Tempest View
2+
title: Major updates to Tempest views
33
description: Tempest 1.5 released with some major improvements to its templating engine
44
author: brent
55
tag: Release
66
---
77

88
Today we released Tempest version 1.5, which includes a bunch of improvements to [Tempest View](/docs/essentials/views), the templating engine that ships by default with the framework. Tempest also has support for Blade and Twig, but we designed Tempest View to take a unique approach to templating with PHP, and I must say: it looks excellent! (I might be biased.)
99

10-
Designing a new language is hard, even if it's "only" a templating language, which is why we marked Tempest View as experimental when Tempest 1.0 released. This meant the package could still change over time, although we try to keep breaking changes at a minimum.
10+
Designing a new language is hard, even if it's "only" a templating language, which is why we marked Tempest View as experimental when Tempest 1.0 released. This meant the package could still change over time, although we try to keep breaking changes at a minimum.
1111

1212
With the release of Tempest 1.5, we did have to make a handful of breaking changes, but overall they shouldn't have a big impact. And I believe both changes are moving the language forward in the right direction. In this post, I want to highlight the new Tempest View features and explain why they needed a breaking change or two.
1313

@@ -34,11 +34,13 @@ And likewise, view components won't have access to variables from the outer scop
3434
```html
3535
<!-- $title will need to be passed in explicitly: -->
3636

37-
<x-post :title="$title"></x-post>
37+
<x-post :title="$title"></x-post>
3838
```
3939

4040
There's one exception to this rule: variables defined by the view itself are directly accessible from within view components. This can be useful when you're using view components that are tied to one specific view, but extracted to a component to avoid code repetition.
4141

42+
:::code-group
43+
4244
```html x-home-highlight.view.php
4345
<div class="<!-- … -->">
4446
{!! $highlights[$name] !!}
@@ -48,21 +50,23 @@ There's one exception to this rule: variables defined by the view itself are dir
4850
<x-home-highlight name="orm" />
4951
```
5052

51-
```php
53+
```php app/HomeController.php
5254
final class HomeController
5355
{
5456
#[Get('/')]
5557
public function __invoke(HighlightRepository $highlightRepository): View
5658
{
5759
return view(
58-
'home.view.php',
60+
'./home.view.php',
5961
highlights: $highlightRepository->all(),
6062
);
6163
}
6264
}
6365
```
6466

65-
Variable scoping now works by compiling view components to PHP closures instead of what we used to do: manage variable scope ourselves. Besides fixing some bugs, it also [simplified view component rendering significantly](https://github.com/tempestphp/tempest-framework/pull/1435), which is great!
67+
:::
68+
69+
Variable scoping now works by compiling view components to PHP closures instead of what we used to do: manage variable scope ourselves. Besides fixing some bugs, it also [simplified view component rendering significantly](https://github.com/tempestphp/tempest-framework/pull/1435), which is great!
6670

6771
## Installable view components
6872

@@ -126,7 +130,7 @@ $original = $session->getOriginalValueFor($name, $default);
126130
</div>
127131
```
128132

129-
While this style might require some getting used to for some people, I think it is the right decision to make: class-based view components had a lot of compiler edge cases that we had to take into account, and often lead to subtle bugs when building new components. I do plan on writing an in-depth post on how to build reusable view components with Tempest soon. Stay tuned for that!
133+
While this style might require some getting used to for some people, I think it is the right decision to make: class-based view components had a lot of compiler edge cases that we had to take into account, and often lead to subtle bugs when building new components. I do plan on writing an in-depth post on how to build reusable view components with Tempest soon. Stay tuned for that!
130134

131135
## Work in progress IDE support
132136

@@ -142,6 +146,6 @@ There is a lot of work to be done, but it's amazing to see this moving forward.
142146

143147
## What's next?
144148

145-
From the beginning I've said that IDE support is a must for any project to succeed. It now looks like there's a real chance of that happening, which is amazing. Besides IDE support, there are a couple of big features to tackle: I want Tempest to ship with some form of "standard component library" that people can use as a scaffold, we're looking into adding HTMX support (or something alike) to build async components, and we plan on making bridges for Laravel and Symfony so that you can use Tempest View in projects outside of Tempest as well.
149+
From the beginning I've said that IDE support is a must for any project to succeed. It now looks like there's a real chance of that happening, which is amazing. Besides IDE support, there are a couple of big features to tackle: I want Tempest to ship with some form of "standard component library" that people can use as a scaffold, we're looking into adding HTMX support (or something alike) to build async components, and we plan on making bridges for Laravel and Symfony so that you can use Tempest View in projects outside of Tempest as well.
146150

147-
If you're inspired and interested to help out with any of these features, then you're more than welcome to [join the Tempest Discord](/discord) and take it from there.
151+
If you're inspired and interested to help out with any of these features, then you're more than welcome to [join the Tempest Discord](/discord) and take it from there.

src/Web/Blog/articles/2025-11-10-route-decorators.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: "Route Decorators in Tempest 2.8"
2+
title: "Route decorators in Tempest 2.8"
33
description: Taking a deep dive in a new Tempest feature
44
author: brent
55
tag: release
@@ -70,7 +70,7 @@ While I really like attribute-based routing, grouping route behavior does feel
7070
- Tempest's default route attributes are represented by HTTP verbs: `#[Get]`, `#[Post]`, etc. Making admin variants for each verb might be tedious, so in my previous example I decided to use one `#[AdminRoute]`, where the verb would be specified manually. There's nothing stopping me from adding `#[AdminGet]`, `#[AdminPost]`, etc; but it doesn't feel super clean.
7171
- When you prefer to namespace admin-specific route attributes like `#[Admin\Get]`, and `#[Admin\Post]`, you end up with naming collisions between normal- and admin versions. I've always found those types of ambiguities to increase cognitive load while coding.
7272
- This approach doesn't really scale: say there are two types of route groups that require a specific middleware (`AuthMiddleware`, for example), then you end up making two or more route attributes, duplicating that logic of adding `AuthMiddleware` to both.
73-
- Say you want nested route groups: one for admin routes and then one for book routes (with a `/admin/books` prefix), you end up with yet another variant called `#[AdminBookRoute]` attribute, not ideal.
73+
- Say you want nested route groups: one for admin routes and then one for book routes (with a `/admin/books` prefix), you end up with yet another variant called `#[AdminBookRoute]` attribute, not ideal.
7474

7575
So… what's the solution? I first looked at Symfony, which also uses attributes for routing:
7676

@@ -95,7 +95,7 @@ class BookAdminController extends AbstractController
9595
}
9696
```
9797

98-
I think Symfony's approach gets us halfway there: it has the benefit of being able to define "shared route behavior" on the controller level, but not across controllers. You could create abstract controllers like `AdminController` and `AdminBookController`, which doesn't scale horizontally when you want to combine multiple route groups, because PHP doesn't have multi-inheritance. On top of that, I also like Tempest's design of using HTTP verbs to model route attributes like `#[Get]` and `#[Post]`, which is missing with Symfony. All of that to say, I like Symfony's approach, but I feel like there's room for improvement.
98+
I think Symfony's approach gets us halfway there: it has the benefit of being able to define "shared route behavior" on the controller level, but not across controllers. You could create abstract controllers like `AdminController` and `AdminBookController`, which doesn't scale horizontally when you want to combine multiple route groups, because PHP doesn't have multi-inheritance. On top of that, I also like Tempest's design of using HTTP verbs to model route attributes like `#[Get]` and `#[Post]`, which is missing with Symfony. All of that to say, I like Symfony's approach, but I feel like there's room for improvement.
9999

100100
With the scene now being set, let's see the design we ended up with in Tempest.
101101

@@ -227,4 +227,4 @@ On top of adding the {b`Tempest\Router\RouteDecorator`} interface, I've also add
227227
- {b`Tempest\Router\WithoutMiddleware`}: which explicitely removes one or more middleware classes from the default middleware stack to all decorated routes.
228228
- {b`Tempest\Router\Stateless`}: which will remove all session and cookie related middleware from the decorated routes.
229229

230-
I really like the solution we ended up with. I think it combines the best of both worlds. Maybe you have some thoughts about it as well? [Join the Tempest Discord](/discord) to let us know! You can also read all the details of route decorators [in the docs](/2.x/essentials/routing#route-decorators-route-groups).
230+
I really like the solution we ended up with. I think it combines the best of both worlds. Maybe you have some thoughts about it as well? [Join the Tempest Discord](/discord) to let us know! You can also read all the details of route decorators [in the docs](/2.x/essentials/routing#route-decorators-route-groups).

src/Web/Blog/index.view.php

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,46 @@
77

88
<x-base title="Blog">
99
<!-- Main container -->
10-
<main class="container px-4 mx-auto xl:px-8 flex flex-col grow isolate">
10+
<main class="isolate flex flex-col mx-auto px-4 xl:px-8 font-mono container grow">
1111
<!-- Main content -->
12-
<div class="grow px-2 w-full lg:pl-12 flex flex-col min-w-0 lg:mt-10">
12+
<div class="flex flex-col lg:mt-10 px-2 lg:pl-12 w-full min-w-0 grow">
1313
<!-- Header -->
1414
<div class="flex flex-col pb-8 max-w-xl">
15-
<h1 class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl lg:text-5xl">Blog</h1>
16-
<p class="mt-4 text-lg text-gray-500 dark:text-gray-400">
15+
<h1 class="font-bold text-gray-900 dark:text-white text-3xl sm:text-4xl lg:text-5xl tracking-tight">Blog</h1>
16+
<p class="mt-4 text-gray-500 dark:text-gray-400 text-lg">
1717
Read the latest news and announcements about Tempest, from framework updates to real-world applications and expert insights.
1818
</p>
19-
<div class="mt-2">
20-
<a href="/rss" class="rounded font-semibold inline-flex items-center focus:outline-hidden disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors px-2.5 py-1 text-sm gap-1.5 ring ring-inset ring-(--ui-border-accented) text-(--ui-text) bg-(--ui-bg) hover:bg-(--ui-bg-elevated) disabled:bg-(--ui-bg) aria-disabled:bg-(--ui-bg) focus-visible:ring-2 focus-visible:ring-(--ui-border-inverted)" rel="noopener noreferrer" target="_blank">
21-
<x-icon name="tabler:rss" class="shrink-0 size-4" />
22-
RSS
19+
<div class="mt-4">
20+
<a href="/rss" class="inline-flex items-center gap-1 bg-gray-300/20 dark:bg-gray-400/10 px-2.5 py-1 rounded font-medium text-gray-700 dark:text-gray-400 text-sm" rel="noopener noreferrer" target="_blank">
21+
<x-icon name="tabler:rss" class="size-4 shrink-0" />
22+
<span>RSS</span>
2323
</a>
2424
</div>
2525
</div>
2626
<!-- Articles -->
27-
<ul class="grid md:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-8 mt-0 mb-8 lg:mt-4 2xl:mt-8">
28-
<li :foreach="$posts as $post" class="flex flex-col justify-between relative border-dashed border border-(--ui-border-accented) hover:bg-(--ui-bg-elevated) rounded-lg p-4 transition">
29-
<a class="absolute inset-0" :href="$post->uri"></a>
30-
<div>
31-
<span class="font-medium">{{ $post->title }}</span>
32-
<p class="text-[15px] text-(--ui-text-muted) mt-1 line-clamp-2">{{ $post->description }}</p>
33-
</div>
34-
<div class="flex items-center mt-6 gap-x-4 justify-between">
35-
<span
36-
:if="$post->tag"
37-
:class="$post->tag->getStyle()"
38-
class="font-medium inline-flex items-center text-xs px-2 py-1 gap-1 rounded ring ring-inset"
39-
>
40-
{{ str($post->tag->value)->title() }}
41-
</span>
42-
<span :if="$post->author" class="text-(--ui-text-muted) text-sm">
43-
by <span class="font-medium">{{ $post->author->getName() }}</span> on <span class="font-medium">{{ $post->createdAt->format('F d, Y') }}</span>
44-
</span>
45-
</div>
46-
</li>
47-
</ul>
27+
<ul class="gap-4 lg:gap-6 grid md:grid-cols-2 lg:grid-cols-3 mt-0 lg:mt-4 2xl:mt-8 mb-8">
28+
<li :foreach="$posts as $post" class="p-0.5 relative border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 rounded-lg transition">
29+
<div class="h-full flex flex-col justify-between border border-dashed border-(--ui-border) p-4 rounded-md">
30+
<a class="absolute inset-0" :href="$post->uri"></a>
31+
<div>
32+
<span class="font-medium">{{ $post->title }}</span>
33+
<p class="text-(--ui-text-dimmed) mt-1 line-clamp-2">{{ $post->description }}</p>
34+
</div>
35+
<div class="flex justify-between items-center gap-x-2 mt-4">
36+
<span
37+
:if="$post->tag"
38+
:class="$post->tag->getStyle()"
39+
class="inline-flex items-center gap-1 px-2 py-1 rounded font-medium text-xs uppercase"
40+
>
41+
{{ str($post->tag->value)->title() }}
42+
</span>
43+
<span :if="$post->author" class="text-(--ui-text-muted) text-sm">
44+
by <span class="font-semibold">{{ $post->author->getName() }}</span> on <span class="font-semibold">{{ $post->createdAt->format('F d, Y') }}</span>
45+
</span>
46+
</div>
47+
</div>
48+
</li>
49+
</ul>
4850
</div>
4951
</main>
5052
</x-base>

src/Web/Blog/show.view.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@
1010
:meta="$post->meta"
1111
>
1212
<!-- Main container -->
13-
<main class="isolate relative flex flex-col items-center mx-auto px-4 xl:px-8 container grow">
13+
<main class="isolate relative flex flex-col items-center mx-auto px-4 xl:px-8 font-mono container grow">
1414
<!-- Main content -->
1515
<article class="flex flex-col lg:mt-10 px-2 w-full md:w-auto min-w-0 max-w-3xl grow">
1616
<!-- Breadcrumbs -->
17-
<nav class="text-(--ui-text-dimmed) font-medium flex items-center mb-4 text-sm gap-x-1.5">
17+
<nav class="text-(--ui-text-dimmed) font-medium flex items-center mb-6 text-sm gap-x-1.5">
1818
<x-icon name="tabler:news" class="mr-1 size-5" />
1919
<a :href="uri([BlogController::class, 'index'])" class="hover:text-(--ui-text) transition">Blog</a>
2020
<span>/</span>
2121
<span class="text-(--ui-primary)">{{ $post->title }}</span>
2222
</nav>
2323
<!-- Header -->
24-
<div class="flex flex-col pb-8 border-b border-(--ui-border) max-w-[65ch]">
25-
<h1 class="font-bold text-gray-900 dark:text-white text-3xl sm:text-4xl lg:text-5xl tracking-tight">
24+
<div class="flex flex-col pb-8 border-b border-(--ui-border) w-full">
25+
<h1 class="max-w-[65ch] font-bold text-3xl sm:text-4xl lg:text-5xl tracking-tight">
2626
{{ $post->title }}
2727
</h1>
28-
<p class="mt-4 text-gray-500 dark:text-gray-400 text-lg">
28+
<p class="mt-4 text-lg text-(--ui-text-muted)">
2929
{{ $post->description }}
3030
</p>
31-
<span :if="$post->author" class="text-(--ui-text-muted) text-sm mt-8">
31+
<span :if="$post->author" class="text-(--ui-text-dimmed) text-sm mt-4">
3232
by <span class="font-medium">{{ $post->author->getName() }}</span> on <span class="font-medium">{{ $post->createdAt->format('F d, Y') }}</span>
3333
</span>
3434
</div>

src/Web/CommandPalette/CommandIndexer.php

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace App\Web\CommandPalette;
66

77
use App\Web\Blog\BlogController;
8+
use App\Web\Community\CommunityController;
89
use App\Web\Documentation\DocumentationController;
910
use App\Web\RedirectsController;
1011
use Override;
@@ -30,6 +31,12 @@ public function index(): ImmutableArray
3031
hierarchy: ['Commands', 'Blog'],
3132
uri: uri([BlogController::class, 'index']),
3233
),
34+
new Command(
35+
title: 'Check out community resources',
36+
type: Type::URI,
37+
hierarchy: ['Commands', 'Community'],
38+
uri: uri([CommunityController::class, 'index']),
39+
),
3340
new Command(
3441
title: 'See the code on GitHub',
3542
type: Type::URI,
@@ -43,16 +50,34 @@ public function index(): ImmutableArray
4350
uri: uri([RedirectsController::class, 'discord']),
4451
),
4552
new Command(
46-
title: 'Follow me on Bluesky',
53+
title: 'Follow Brent from the core team on Bluesky',
4754
type: Type::URI,
48-
hierarchy: ['Commands', 'Link'],
49-
uri: uri([RedirectsController::class, 'bluesky']),
55+
hierarchy: ['Commands', 'Link', 'Core team'],
56+
uri: uri([RedirectsController::class, 'blueskyBrent']),
5057
),
5158
new Command(
52-
title: 'Follow me on X',
59+
title: 'Follow Brent from the core team on X',
5360
type: Type::URI,
54-
hierarchy: ['Commands', 'Link'],
55-
uri: uri([RedirectsController::class, 'twitter']),
61+
hierarchy: ['Commands', 'Link', 'Core team'],
62+
uri: uri([RedirectsController::class, 'twitterBrent']),
63+
),
64+
new Command(
65+
title: 'Follow Enzo from the core team on Bluesky',
66+
type: Type::URI,
67+
hierarchy: ['Commands', 'Link', 'Core team'],
68+
uri: uri([RedirectsController::class, 'blueskyEnzo']),
69+
),
70+
new Command(
71+
title: 'Follow Enzo from the core team on X',
72+
type: Type::URI,
73+
hierarchy: ['Commands', 'Link', 'Core team'],
74+
uri: uri([RedirectsController::class, 'twitterEnzo']),
75+
),
76+
new Command(
77+
title: 'Follow Aidan from the core team on X',
78+
type: Type::URI,
79+
hierarchy: ['Commands', 'Link', 'Core team'],
80+
uri: uri([RedirectsController::class, 'twitterAidan']),
5681
),
5782
new Command(
5883
title: 'Toggle dark mode',

0 commit comments

Comments
 (0)