Some clean up stuff
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { provideZonelessChangeDetection } from '@angular/core';
|
import { Component, provideZonelessChangeDetection } from '@angular/core';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { provideTranslateService } from '@ngx-translate/core';
|
import { provideTranslateService } from '@ngx-translate/core';
|
||||||
@@ -172,3 +173,42 @@ describe('AboutComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Material typography (`brand-family` in styles.scss) and the Tailwind body
|
||||||
|
* stack are declared in two separate places and are easy to change apart. When
|
||||||
|
* they disagree, only visitors who happen to have the extra font installed
|
||||||
|
* locally see it, so it will not show up in review.
|
||||||
|
*/
|
||||||
|
describe('global font stack', () => {
|
||||||
|
@Component({
|
||||||
|
imports: [MatCardModule],
|
||||||
|
template: `<mat-card><mat-card-title>probe</mat-card-title></mat-card>`,
|
||||||
|
})
|
||||||
|
class FontProbeComponent {}
|
||||||
|
|
||||||
|
function normalizeStack(value: string): string {
|
||||||
|
return value
|
||||||
|
.replace(/["']/g, '')
|
||||||
|
.split(',')
|
||||||
|
.map(family => family.trim())
|
||||||
|
.filter(family => family.length > 0)
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
it('resolves Material card titles to the same font stack as body copy', async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [FontProbeComponent],
|
||||||
|
providers: [provideZonelessChangeDetection()],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
const probe = TestBed.createComponent(FontProbeComponent);
|
||||||
|
probe.detectChanges();
|
||||||
|
|
||||||
|
const title = probe.nativeElement.querySelector('.mat-mdc-card-title') as HTMLElement;
|
||||||
|
const titleStack = normalizeStack(getComputedStyle(title).fontFamily);
|
||||||
|
const bodyStack = normalizeStack(getComputedStyle(document.body).fontFamily);
|
||||||
|
|
||||||
|
expect(titleStack).toBe(bodyStack);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -14,12 +14,15 @@ describe('LanguageService', () => {
|
|||||||
return TestBed.inject(LanguageService);
|
return TestBed.inject(LanguageService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const documentLanguage = document.documentElement.lang;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
document.documentElement.lang = documentLanguage;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses the stored language as the initial value', () => {
|
it('uses the stored language as the initial value', () => {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
"PROFILE": "Profilfoto von Andreas Dahm"
|
"PROFILE": "Profilfoto von Andreas Dahm"
|
||||||
},
|
},
|
||||||
"HELLO": "Hallo, ich bin Andreas.",
|
"HELLO": "Hallo, ich bin Andreas.",
|
||||||
"LEAD": "Ich arbeite seit 2010 als Softwareentwickler. Die meiste Zeit davon in zwei ziemlich unterschiedlichen Ecken. Die eine ist Echtzeit-3D-Simulation: Stoffphysik, Kollisionserkennung, GPU-Shader. Die andere ist Cloud-Architektur für größere Unternehmensplattformen. Ein paar Jahre habe ich ein kleines Backend-Team geleitet, den Umbau eines großen Legacy-Monolithen Richtung Microservices geplant und zusammen mit meinem Bruder ein Spiel auf Steam veröffentlicht. In letzter Zeit geht viel Zeit in Tooling rund um KI-gestützte Entwicklung, vor allem darum, das Ergebnis überprüfbar zu machen und nicht nur schnell. Privat baue ich Spiele und probiere Sachen mit Algorithmen aus. Das meiste davon landet irgendwann auf dieser Seite.",
|
"LEAD": "Ich arbeite seit 2010 als Softwareentwickler, die meiste Zeit davon in zwei recht unterschiedlichen Bereichen. Der eine ist Echtzeit-3D-Simulation: Stoffphysik, Kollisionserkennung, GPU-Shader. Der andere ist Cloud-Architektur für größere Unternehmensplattformen. Ein paar Jahre habe ich ein kleines Backend-Team geleitet, den Umbau eines großen Legacy-Monolithen Richtung Microservices geplant und zusammen mit meinem Bruder ein Spiel auf Steam veröffentlicht. Zuletzt stecke ich viel Zeit in Tooling rund um KI-gestützte Entwicklung, vor allem in die Frage, wie das Ergebnis überprüfbar bleibt. Privat baue ich Spiele und probiere Sachen mit Algorithmen aus. Das meiste davon landet irgendwann auf dieser Seite.",
|
||||||
"ROLE": "Senior Software Entwickler / Full-Stack Entwickler / Softwarearchitekt",
|
"ROLE": "Senior Software Entwickler / Full-Stack Entwickler / Softwarearchitekt",
|
||||||
"LOCATION": "München · Remote",
|
"LOCATION": "München · Remote",
|
||||||
"VIEW_PROJECTS": "Projekte ansehen",
|
"VIEW_PROJECTS": "Projekte ansehen",
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
"PROJECT": {
|
"PROJECT": {
|
||||||
"CLAUDE_BOX": {
|
"CLAUDE_BOX": {
|
||||||
"TITLE": "Claude-in-a-Box",
|
"TITLE": "Claude-in-a-Box",
|
||||||
"DESCRIPTION": "Eine Entwicklungsumgebung im Container, in der ein KI-Coding-Agent nach festen Regeln arbeitet statt nach spontanen Prompts. Er zieht sich die Regeln für den Stack, den er im Repository vorfindet, und ein Git-Hook stoppt den Commit, wenn Build oder Tests rot sind.",
|
"DESCRIPTION": "Eine Entwicklungsumgebung im Container, in der ein KI-Coding-Agent nach einem festen Regelsatz arbeitet und nicht nach dem, was ich gerade eintippe. Er zieht sich die Regeln für den Stack, den er im Repository vorfindet, und ein Git-Hook stoppt den Commit, wenn Build oder Tests rot sind.",
|
||||||
"LINK_INTERNAL": "Projektdetails",
|
"LINK_INTERNAL": "Projektdetails",
|
||||||
"HIGHLIGHTS": {
|
"HIGHLIGHTS": {
|
||||||
"P1": "Der Agent lädt die Regeln für den Stack, den er im Repo vorfindet. Ein Java-Service bekommt also andere als ein Frontend.",
|
"P1": "Der Agent lädt die Regeln für den Stack, den er im Repo vorfindet. Ein Java-Service bekommt also andere als ein Frontend.",
|
||||||
@@ -364,17 +364,17 @@
|
|||||||
},
|
},
|
||||||
"CLAUDE_BOX": {
|
"CLAUDE_BOX": {
|
||||||
"TITLE": "Claude-in-a-Box",
|
"TITLE": "Claude-in-a-Box",
|
||||||
"SHORT_DESCRIPTION": "Ein Container-Setup, um mit einem KI-Coding-Agenten nach festen Regeln zu arbeiten.",
|
"SHORT_DESCRIPTION": "Ein Container-Setup, um mit einem KI-Coding-Agenten nach einem festen Regelsatz zu arbeiten.",
|
||||||
"INTRODUCTION": "Einen KI-Coding-Agenten dazu zu bringen, irgendetwas zu produzieren, ist einfach. Ihn dazu zu bringen, etwas zu produzieren, das man auch wirklich mergen würde, ist eine andere Sache, weil das Ergebnis auf den ersten Blick meistens ganz vernünftig aussieht. Also habe ich ein Docker-Setup gebaut, in dem der Agent wie jeder andere Mitwirkende arbeitet. Er bekommt die Architekturregeln des Repositories, in dem er gerade ist, und der Build muss grün sein, bevor überhaupt etwas committet werden kann. Die meiste Arbeit steckt im Setup rund um den Agenten und nicht in den Prompts.",
|
"INTRODUCTION": "Ein KI-Coding-Agent produziert den ganzen Tag Code, ohne sich zu beschweren. Das Problem ist, dass das Ergebnis meistens vernünftig aussieht, auch wenn es falsch ist, und die ganzen Kosten damit im Review landen. Also habe ich ein Docker-Setup gebaut, das den Agenten denselben Regeln unterwirft wie alle anderen, die am Repository arbeiten. Er liest die Architekturregeln des Projekts, in dem er gerade steckt, und der Build muss durchlaufen, bevor ein Commit möglich ist. Auf den Hook, der genau das durchsetzt, komme ich immer wieder zurück, weil er das Einzige ist, woran der Agent nicht vorbeikommt.",
|
||||||
"BULLET_1": "Container mit festen Toolchains: JDKs und Maven über SDKMAN, Node und ein Home-Volume, das einen Rebuild übersteht.",
|
"BULLET_1": "Container mit festen Toolchains: JDKs und Maven über SDKMAN, Node und ein Home-Volume, das einen Rebuild übersteht.",
|
||||||
"BULLET_2": "Die Regeln werden per Erkennung geladen. Ein Repo mit pom.xml bekommt die Java- und Spring-Regeln, eines mit package.json die TypeScript-Regeln, und Repos mit eigenen Architekturnotizen bekommen diese zusätzlich.",
|
"BULLET_2": "Die Regeln werden per Erkennung geladen. Ein Repo mit pom.xml bekommt die Java- und Spring-Regeln, eines mit package.json die TypeScript-Regeln, und Repos mit eigenen Architekturnotizen bekommen diese zusätzlich.",
|
||||||
"BULLET_3": "Ein Commit-Hook ermittelt den Stack, führt den passenden Build und die Tests aus und verweigert den Commit, wenn sie fehlschlagen.",
|
"BULLET_3": "Ein Commit-Hook ermittelt den Stack, führt den passenden Build und die Tests aus und verweigert den Commit, wenn sie fehlschlagen.",
|
||||||
"BULLET_4": "MCP-Server für Code-Graph-Abfragen und lesende Jira-Zugriffe, dazu Review-Agenten, die einen fertigen Diff prüfen, bevor er als erledigt gilt.",
|
"BULLET_4": "MCP-Server für Code-Graph-Abfragen und lesende Jira-Zugriffe, dazu Review-Agenten, die einen fertigen Diff prüfen, bevor er als erledigt gilt.",
|
||||||
"CHALLENGE_1": "Die Regeln kurz genug zu halten, dass sie noch etwas bringen. Lange allgemeine Richtlinien werden einfach ignoriert, also sind sie in Dateien pro Stack und pro Repo aufgeteilt, die nur bei Bedarf geladen werden.",
|
"CHALLENGE_1": "Die Regeln kurz genug zu halten, dass sie noch etwas bringen. Lange allgemeine Richtlinien werden einfach ignoriert, also sind sie in Dateien pro Stack und pro Repo aufgeteilt, die nur bei Bedarf geladen werden.",
|
||||||
"CHALLENGE_2": "Das Quality Gate so zu bauen, dass der Agent sich nicht daran vorbeireden kann. Die Prüfung läuft als Git-Hook außerhalb des Agenten, ein fehlgeschlagener Build blockiert den Commit also unabhängig davon, was der Agent über seine eigene Arbeit denkt.",
|
"CHALLENGE_2": "Das Quality Gate so zu bauen, dass der Agent sich nicht daran vorbeireden kann. Die Prüfung läuft als Git-Hook außerhalb des Agenten, ein fehlgeschlagener Build blockiert den Commit also unabhängig davon, was der Agent über seine eigene Arbeit denkt.",
|
||||||
"CHALLENGE_3": "Die Toolchains in einem Container ohne Root installiert zu bekommen. Builds, Browser und Sprach-Runtimes mussten alle in Volumes, die dem Entwickler-User gehören.",
|
"CHALLENGE_3": "Die Toolchains in einem Container ohne Root zum Laufen zu bringen. Builds, Browser und Sprach-Runtimes mussten alle in Volumes landen, die dem Entwickler-User gehören.",
|
||||||
"LEARNING_1": "Wie gut das Ganze funktioniert, hängt viel stärker am Setup rund um den Agenten als an der Formulierung der Prompts.",
|
"LEARNING_1": "Die Formulierung der Prompts war deutlich weniger wichtig, als ich dachte. Fast der ganze Unterschied kam vom Drumherum.",
|
||||||
"LEARNING_2": "Den Output des Agenten genauso zu reviewen und zu gaten wie den Pull Request einer neuen Kollegin hat sich als die nützlichste Regel überhaupt herausgestellt."
|
"LEARNING_2": "Den Output des Agenten genauso zu reviewen und durch dieselben Gates zu schicken wie den Pull Request eines neuen Teammitglieds hat sich als die nützlichste Regel überhaupt herausgestellt."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"IMPRINT": {
|
"IMPRINT": {
|
||||||
|
|||||||
@@ -187,7 +187,7 @@
|
|||||||
"PROJECT": {
|
"PROJECT": {
|
||||||
"CLAUDE_BOX": {
|
"CLAUDE_BOX": {
|
||||||
"TITLE": "Claude-in-a-Box",
|
"TITLE": "Claude-in-a-Box",
|
||||||
"DESCRIPTION": "A development setup in a container where an AI coding agent works under fixed rules instead of ad hoc prompting. It picks up the rules for whatever stack it finds in the repository, and a git hook stops the commit if the build or the tests are red.",
|
"DESCRIPTION": "A development setup in a container where an AI coding agent works under a fixed set of rules rather than whatever I happen to type that day. It picks up the rules for whatever stack it finds in the repository, and a git hook stops the commit if the build or the tests are red.",
|
||||||
"LINK_INTERNAL": "Project details",
|
"LINK_INTERNAL": "Project details",
|
||||||
"HIGHLIGHTS": {
|
"HIGHLIGHTS": {
|
||||||
"P1": "The agent loads the rules for the stack it finds in the repo, so a Java service and a frontend get different ones.",
|
"P1": "The agent loads the rules for the stack it finds in the repo, so a Java service and a frontend get different ones.",
|
||||||
@@ -364,8 +364,8 @@
|
|||||||
},
|
},
|
||||||
"CLAUDE_BOX": {
|
"CLAUDE_BOX": {
|
||||||
"TITLE": "Claude-in-a-Box",
|
"TITLE": "Claude-in-a-Box",
|
||||||
"SHORT_DESCRIPTION": "A container setup for working with an AI coding agent under fixed rules.",
|
"SHORT_DESCRIPTION": "A container setup for working with an AI coding agent under a fixed set of rules.",
|
||||||
"INTRODUCTION": "Getting an AI coding agent to produce something is easy. Getting it to produce something you would actually merge is a different problem, because the output usually looks fine at first glance. So I built a Docker setup where the agent works like any other contributor. It gets the architecture rules of the repository it is in, and the build has to be green before anything can be committed. Most of the work went into the setup around the agent rather than into the prompts.",
|
"INTRODUCTION": "An AI coding agent will produce code all day without complaining. The trouble is that what comes out usually looks reasonable even when it is wrong, so all the cost lands in review. I built a Docker setup that puts the agent under the same constraints as anyone else working on the repo. It reads the architecture rules of whatever project it is in, and the build has to pass before a commit goes through. The hook that enforces that last part is the piece I keep coming back to, because it is the one thing the agent has no way around.",
|
||||||
"BULLET_1": "Container with fixed toolchains: JDKs and Maven through SDKMAN, Node, and a home volume that survives a rebuild.",
|
"BULLET_1": "Container with fixed toolchains: JDKs and Maven through SDKMAN, Node, and a home volume that survives a rebuild.",
|
||||||
"BULLET_2": "Rules are loaded by detection. A repo with a pom.xml gets the Java and Spring rules, one with a package.json gets the TypeScript rules, and repos with their own architecture notes get those on top.",
|
"BULLET_2": "Rules are loaded by detection. A repo with a pom.xml gets the Java and Spring rules, one with a package.json gets the TypeScript rules, and repos with their own architecture notes get those on top.",
|
||||||
"BULLET_3": "A commit hook works out the stack, runs the matching build and tests, and refuses the commit if they fail.",
|
"BULLET_3": "A commit hook works out the stack, runs the matching build and tests, and refuses the commit if they fail.",
|
||||||
@@ -373,7 +373,7 @@
|
|||||||
"CHALLENGE_1": "Keeping the rules short enough to stay useful. Long general guidelines just get ignored, so they are split into per-stack and per-repo files that only load when they apply.",
|
"CHALLENGE_1": "Keeping the rules short enough to stay useful. Long general guidelines just get ignored, so they are split into per-stack and per-repo files that only load when they apply.",
|
||||||
"CHALLENGE_2": "Making the quality gate something the agent cannot talk its way past. The check runs as a git hook outside the agent, so a failing build blocks the commit no matter what the agent believes about its own work.",
|
"CHALLENGE_2": "Making the quality gate something the agent cannot talk its way past. The check runs as a git hook outside the agent, so a failing build blocks the commit no matter what the agent believes about its own work.",
|
||||||
"CHALLENGE_3": "Getting the toolchains installed in a container that has no root. Builds, browsers and language runtimes all had to go into volumes owned by the development user.",
|
"CHALLENGE_3": "Getting the toolchains installed in a container that has no root. Builds, browsers and language runtimes all had to go into volumes owned by the development user.",
|
||||||
"LEARNING_1": "How well this works depends far more on the setup around the agent than on how the prompt is worded.",
|
"LEARNING_1": "Prompt wording mattered far less than I expected. Almost all of the difference came from the scaffolding.",
|
||||||
"LEARNING_2": "Reviewing and gating the agent's output the same way you would a new colleague's pull request turned out to be the most useful rule of the lot."
|
"LEARNING_2": "Reviewing and gating the agent's output the same way you would a new colleague's pull request turned out to be the most useful rule of the lot."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; }
|
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; }
|
||||||
.sans { font-family: Inter, Roboto, Arial, sans-serif; }
|
.sans { font-family: Roboto, Arial, sans-serif; }
|
||||||
.lbl { fill: #8b97a6; font-size: 17px; letter-spacing: .04em; }
|
.lbl { fill: #8b97a6; font-size: 17px; letter-spacing: .04em; }
|
||||||
.chip { fill: #cdd6e0; font-size: 19px; }
|
.chip { fill: #cdd6e0; font-size: 19px; }
|
||||||
.cap { fill: #6c7887; font-size: 15px; letter-spacing: .18em; }
|
.cap { fill: #6c7887; font-size: 15px; letter-spacing: .18em; }
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// ---- Themes ----
|
// ---- Themes ----
|
||||||
$light-theme: mat.define-theme((color: (theme-type: light, primary: mat.$cyan-palette, tertiary: mat.$orange-palette ),
|
$light-theme: mat.define-theme((color: (theme-type: light, primary: mat.$cyan-palette, tertiary: mat.$orange-palette ),
|
||||||
typography: (brand-family: 'Inter, Roboto, Arial, sans-serif', bold-weight: 600),
|
typography: (brand-family: 'Roboto, Arial, sans-serif', bold-weight: 600),
|
||||||
density: (scale: 0),
|
density: (scale: 0),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user