Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
d620b82
Install DotNetBoost.Auditing beside the other three
sadeqabuhattem Oct 23, 2026
028cfad
Move Aspire to 13.6.4, which answers the MessagePack advisory
sadeqabuhattem Oct 24, 2026
b86de06
Read the trail from the dashboard
sadeqabuhattem Oct 24, 2026
5b7cf74
Photograph the Audit page
sadeqabuhattem Oct 24, 2026
3f96936
Put the test-utils bump back, and say what the failure actually was
sadeqabuhattem Oct 26, 2026
019f093
Merge fix-messagepack-advisory into add-auditing-library
sadeqabuhattem Oct 26, 2026
c969828
Take the test-utils bump back out of the bin
sadeqabuhattem Oct 26, 2026
6ade153
Sign every request in as one sample user, and drop the "acting as" box
sadeqabuhattem Oct 26, 2026
e954d0a
Create the audit tables in a database that predates them
sadeqabuhattem Oct 26, 2026
0f1827e
Add a sort and a date range picker to the Audit page, and stop the ta…
sadeqabuhattem Oct 26, 2026
dd93010
Number the Audit page's pages instead of walking them Newer/Older
sadeqabuhattem Oct 26, 2026
91cf3fc
Record the trail with Auditing's interceptor, on DotNetBoost.Auditing…
sadeqabuhattem Oct 26, 2026
6631754
Record a settings save as one audit entry for the group, not one per row
sadeqabuhattem Oct 26, 2026
839f7af
Store a settings group's audit entries under the name Setting
sadeqabuhattem Oct 26, 2026
816fb13
Keep the audit trail for as long as a settings group says, and prune …
sadeqabuhattem Oct 26, 2026
445d70a
Rebuild the dashboard on shadcn-vue, after its dashboard-01 and sideb…
sadeqabuhattem Oct 26, 2026
999ac0c
Set the dashboard in Geist, the face shadcn-vue's blocks use, and kee…
sadeqabuhattem Oct 26, 2026
9200a78
Draw card titles and the store name at the sizes the dashboard-01 blo…
sadeqabuhattem Oct 26, 2026
7441266
Frame the dashboard's tables the way the dashboard-01 block does
sadeqabuhattem Oct 26, 2026
7697edd
Sort the Audit table from its column headers, and give it a Columns m…
sadeqabuhattem Oct 26, 2026
909dcab
Group the sidebar's pages by what they are for, with Catalog's and Se…
sadeqabuhattem Oct 26, 2026
99294c7
Line the Arabic sidebar up with the English one, and fix the other ph…
sadeqabuhattem Oct 27, 2026
66fbc93
Sort the Products and Categories tables from their headers, with a Co…
sadeqabuhattem Oct 27, 2026
f09c70e
Search and filter the products list by category, in the API and on th…
sadeqabuhattem Oct 27, 2026
2ddce8f
Put the Columns button over each catalogue table's end corner, as Aud…
sadeqabuhattem Oct 27, 2026
4f00630
Put Audit before Settings in the sidebar, and set every text in the o…
sadeqabuhattem Oct 27, 2026
85110cd
Put the New buttons after Columns over the catalogue tables, and filt…
sadeqabuhattem Oct 27, 2026
c2f0e7e
Lay the Audit filters out in one full-width row, and gray the action …
sadeqabuhattem Oct 27, 2026
404fc3b
Draw the sidebar's search as the blocks' search field
sadeqabuhattem Oct 27, 2026
951ab70
Give the media library a list view beside the grid, with page sizes a…
sadeqabuhattem Oct 27, 2026
3078305
Filter Languages by status with tabs, and give it a Columns menu and …
sadeqabuhattem Oct 27, 2026
4270e87
List the settings groups down the start side as cards, instead of a t…
sadeqabuhattem Oct 27, 2026
74a1765
Say four libraries on the Overview, now that Auditing is one of them
sadeqabuhattem Oct 28, 2026
f2da0b3
Preview the chosen image in the upload dialog
sadeqabuhattem Oct 28, 2026
40a30b2
Bring the composition counts and library counts in comments up to fou…
sadeqabuhattem Oct 28, 2026
01c59da
Test that the header's language selector moves the labels, and falls …
sadeqabuhattem Oct 28, 2026
969af79
Cut the history and repetition out of Program.cs's and BrandingSettin…
sadeqabuhattem Oct 28, 2026
d6a607b
Cut the history and repetition out of DependencyInjection.cs's and th…
sadeqabuhattem Oct 28, 2026
7678c45
Halve the README, and correct what it said about the Audit page, sign…
sadeqabuhattem Oct 28, 2026
a8644dd
Split the Catalog and Audit pages into a component per tab and per table
sadeqabuhattem Oct 28, 2026
fcebb83
Test that the products listing leaves its page size to the API, debou…
sadeqabuhattem Oct 28, 2026
7959ebf
Load vue-sonner's stylesheet, so toasts are seen again
sadeqabuhattem Oct 28, 2026
ce3e713
Save a settings group's audit entry through a scope of its own
sadeqabuhattem Oct 28, 2026
2fd6150
Add a browser smoke test, so an invisible toast fails a check
sadeqabuhattem Oct 28, 2026
89e5931
Retake the dashboard screenshots on shadcn-vue
sadeqabuhattem Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
26 changes: 22 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The sample's claims, checked on a machine that is not the author's.
#
# This repository argues that what it says is tested rather than asserted — the three libraries
# This repository argues that what it says is tested rather than asserted — the four libraries
# never reference each other, no endpoint's response changes with the base language, every
# published route is called, and even the counts in the README are pinned. All of that was only
# ever true on one laptop until this file existed.
Expand Down Expand Up @@ -49,7 +49,7 @@ jobs:
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: nuget-${{ runner.os }}-

# The three DotNetBoost libraries come from nuget.org like anyone else's — there is no
# The four DotNetBoost libraries come from nuget.org like anyone else's — there is no
# project reference to them and no local feed, which is the point of the sample.
- name: Restore
run: dotnet restore
Expand All @@ -60,8 +60,8 @@ jobs:
# ubuntu-latest ships a running Docker daemon, which Testcontainers uses to start the real
# PostgreSQL this suite needs — twice, since the integration classes boot the application in
# both language modes. Without a runtime those tests SKIP rather than fail, so a green run
# here that skipped 95 of them would be no signal at all; the step after this one says which
# happened.
# here that skipped 109 of them would be no signal at all; the step after this one says
# which happened.
- name: Test
run: |
dotnet test DotNetBoostSample.slnx \
Expand Down Expand Up @@ -125,3 +125,21 @@ jobs:
# each suite exists because of a bug the .NET side could not have caught.
- name: Test
run: npm test

# Only Chromium, and with the system libraries it needs on a bare runner. The version is the
# one the lockfile's @playwright/test was built against, so the browser and the runner agree.
- name: Install Chromium
run: npx playwright install --with-deps chromium

# The browser smoke test. No API and no database: every `/api/**` call is answered from the
# browser by test/e2e/api.ts. It is here for what the three steps above cannot see — a toast
# nobody can see was exactly that, and every one of them passed.
- name: Browser smoke test
run: npm run test:e2e

- name: Upload Playwright traces
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-traces
path: clients/dashboard/test-results/
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"shadcn-vue": {
"command": "npx",
"args": ["-y", "shadcn-vue@2.8.2", "mcp", "-c", "clients/dashboard"]
}
}
}
25 changes: 16 additions & 9 deletions ARCHITECTURE-SLICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ the two disagreed the code won — §10 lists what changed on contact with the c
written while the work sat on `feat/vertical-slices-cqrs` and still argues with itself about
whether to merge it; §11.3 says how that ended.

The solution builds clean on `net8.0` and `net10.0` with `TreatWarningsAsErrors`, and all 121 tests
The solution builds clean on `net8.0` and `net10.0` with `TreatWarningsAsErrors`, and all 160 tests
pass, including the integration suite that boots the real `Program.cs` against a throwaway
PostgreSQL and drives every route over HTTP. No endpoint's URL, status code or JSON shape changed.

Expand Down Expand Up @@ -46,7 +46,7 @@ Three reasons specific to this repo, not general taste:

**And a caveat on multi-targeting.** The *libraries* below can target `net8.0;net10.0`. The **host
cannot today**: `Microsoft.EntityFrameworkCore` / `Npgsql.EntityFrameworkCore.PostgreSQL` 10.0.3 and
Aspire 13.5.3 all require `net10.0`. §7 has the conditional-version pattern if you want the host on
Aspire 13.6.4 all require `net10.0`. §7 has the conditional-version pattern if you want the host on
both too; the value is mostly in the class libraries, where multi-targeting proves the slices
compile against DotNetBoost's own `net8.0` TFM.

Expand All @@ -55,7 +55,12 @@ compile against DotNetBoost's own `net8.0` TFM.
## 1. One concern, stated once

This repo was **1,788 lines of C# in 13 files** when this was written, and the README's thesis is
that the integration between three independent libraries is *five files, none of them large*.
that the integration between three independent libraries is *five files, none of them large*. A
fourth library has since been installed, and that count is now nine files — see the README; the
reasoning below is unaffected. The one new file it would have changed its mind about,
`Composition/CreationTrail.cs` — a creation's audit entry, written by hand in a scope of its own —
is gone again: DotNetBoost.Auditing 1.0.0-preview.2 added `IgnoreEntity<T>()`, its capture is now
attached to the context, and its place on the list is one line of `Infrastructure/DependencyInjection.cs`.
Clean architecture plus vertical slices turns one project into four and moves the composition
behind a dispatcher. The behaviour does not change. For a monolith heading to a hundred endpoints
that is the right trade; for a sample whose job is to make a composition claim visible in one
Expand Down Expand Up @@ -190,9 +195,10 @@ src/DotNetBoostSample.Api/
appsettings*.json
```

Eleven slices, each one file. The three library endpoint sets (`/api/settings`, `/api/media`,
`/api/languages`, `/api/translations`) are untouched — they are still four `Map*Endpoints()` calls
in `Program.cs`, and this architecture does not reach them.
Eleven slices, each one file. The library endpoint sets (`/api/settings`, `/api/media`,
`/api/languages`, `/api/translations`, and `/api/audit` since a fourth library was installed) are
untouched — they are still one `Map*Endpoints()` call each in `Program.cs`, five of them now, and
this architecture does not reach them.

---

Expand Down Expand Up @@ -435,11 +441,12 @@ public static class ListProducts
public void Map(IEndpointRouteBuilder app) =>
app.MapGet("/api/products", async (
int? categoryId,
string? search,
int? page,
int? pageSize,
IDispatcher dispatcher,
CancellationToken ct) =>
await dispatcher.SendAsync(new Query(categoryId, page, pageSize), ct))
await dispatcher.SendAsync(new Query(categoryId, search, page, pageSize), ct))
.WithTags("Products")
.WithSummary("One page of products, translated, with per-language image alt text");
}
Expand Down Expand Up @@ -660,7 +667,7 @@ about four queries per page could be a unit test rather than a comment.
| **Projects and files** | 1 → 4 projects, and 13 → 42 C# files in `src/`. The eleven slices account for eleven of the twenty-nine new ones; the rest is the dispatcher, the result type, the endpoint scan, four shared records, two EF configurations, and the refusal handling §10a added after this table was first written — the validation and `DbUpdate` handlers, the error-translator interface and its Postgres implementation. Nothing it does is new. |
| **Indirection** | A route no longer shows you what it does. `MapGet → IDispatcher → Wrapper<,> → behaviors → Handler` is four hops for a query whose body is unchanged, and the stack trace in a debugger gets a reflection frame in the middle. |
| **Reflection** | Handler scanning and `MakeGenericType` at first dispatch. Fine for a server; a blocker under native AOT, where the answer is a source-generated dispatcher. |
| **Reach** | Covers 11 of the application's routes. The three libraries' own endpoints are not slices and will never be — so the dashboard talks to two different architectures, which is honest but worth knowing. |
| **Reach** | Covers 11 of the application's routes. The libraries' own endpoints are not slices and will never be — so the dashboard talks to two different architectures, which is honest but worth knowing. |
| **Multi-targeting** | Two compiles, two analyzer passes, a CI matrix, and conditional EF versions. Buys you proof that the slices build against DotNetBoost's `net8.0` TFM. |
| **The thesis** | The README's claim is "five files, none of them large." It is still true after this, and `Composition/` makes it more visible — but a reader now meets a dispatcher, a result type and four projects on the way to it. |

Expand Down Expand Up @@ -692,7 +699,7 @@ that the package layout made the layering mistake unbuildable rather than merely

**`AddInfrastructure` takes strings, not `IConfiguration`.** Reading configuration is the host's
job, so `Program.cs` resolves the connection string and the media root and passes both. That keeps
Infrastructure's package list to EF Core, the three libraries' stores and the Npgsql provider — no
Infrastructure's package list to EF Core, the libraries' stores and the Npgsql provider — no
`Microsoft.Extensions.Configuration`, no `Hosting` — and it means the "run the AppHost" message for
a missing connection string is raised where it can say something useful.

Expand Down
43 changes: 39 additions & 4 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@
</PropertyGroup>

<!-- ──────────────────────────────────────────────────────────────────────────
The three DotNetBoost libraries, straight from nuget.org.
The four DotNetBoost libraries, straight from nuget.org.

Note what is NOT here: no DotNetBoost.Core, no DotNetBoost.Abstractions, no
integration package pairing two of them. Each library is installed on its own
and knows nothing about the others. tests/ asserts that stays true.

All four happen to be on 1.0.0-preview.2 today, and nothing aligns those numbers:
Auditing reached preview.2 a release after the other three, for a fix of its own
(IgnoreEntity<T>(), below). Four independently versioned libraries are four release
cadences, which is the other half of what "no shared kernel" means.
────────────────────────────────────────────────────────────────────────── -->
<ItemGroup Label="DotNetBoost — Settings">
<PackageVersion Include="DotNetBoost.Settings.Core" Version="1.0.0-preview.2" />
Expand Down Expand Up @@ -37,6 +42,21 @@
<PackageVersion Include="DotNetBoost.MultiLingual.Localization" Version="1.0.0-preview.2" />
</ItemGroup>

<ItemGroup Label="DotNetBoost — Auditing">
<!-- Who changed what, when, and from what to what. The engine plus the attributes; nothing
here is referenced by the other three, and nothing here references them. -->
<PackageVersion Include="DotNetBoost.Auditing.Core" Version="1.0.0-preview.2" />
<!-- The SaveChangesInterceptor that reads the change tracker, the EF Core store, and the two
tables. It asks for the same EF Core versions pinned below — 8.0.30 and 10.0.11 — so
adding it moved nothing in the persistence block. -->
<PackageVersion Include="DotNetBoost.Auditing.EntityFrameworkCore" Version="1.0.0-preview.2" />
<!-- The /api/audit endpoints, and the HttpContext-backed actor accessor that records who made
each change. Not here: DotNetBoost.Auditing.Dapper and .MongoDb, which are the other two
stores the library ships — this application keeps the trail in the PostgreSQL it already
has, in the DbContext it already has, which is what buys the transaction guarantee. -->
<PackageVersion Include="DotNetBoost.Auditing.API" Version="1.0.0-preview.2" />
</ItemGroup>

<!-- ──────────────────────────────────────────────────────────────────────────
Persistence — PostgreSQL through EF Core, one version per target framework.

Expand Down Expand Up @@ -77,10 +97,25 @@
<PackageVersion Include="FluentValidation" Version="12.1.1" />
</ItemGroup>

<!-- ──────────────────────────────────────────────────────────────────────────
Aspire — the AppHost and its two integrations.

All three move together, and so does the Aspire.AppHost.Sdk version in the AppHost csproj:
the SDK generates the `Projects.*` handles the hosting packages consume, so a version split
between the two is not a combination anybody tests.

13.6.2 is the floor here, not a preference. Below it the AppHost resolves MessagePack
2.5.302 transitively, through StreamJsonRpc, and 2.5.302 is the subject of
GHSA-qhrr-8q5h-9q3h (CVE-2026-92707, moderate) — which NuGet's audit reports as NU1902 and
TreatWarningsAsErrors turns into a restore that fails before a single file compiles.
13.6.2 was the first release to depend on MessagePack 2.5.307 directly, and a package's own
dependency sits nearer than one it inherits, so the advisory is answered by the version
chosen here rather than by a direct reference to something only Aspire uses.
────────────────────────────────────────────────────────────────────────── -->
<ItemGroup Label="Aspire — AppHost (orchestration)">
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.5.3" />
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="13.5.3" />
<PackageVersion Include="Aspire.Hosting.JavaScript" Version="13.5.3" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.6.4" />
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="13.6.4" />
<PackageVersion Include="Aspire.Hosting.JavaScript" Version="13.6.4" />
</ItemGroup>

<ItemGroup Label="Aspire — service defaults and integrations">
Expand Down
Loading
Loading