Format all markdown files in active directories

This commit is contained in:
Aaron Po
2026-04-27 18:05:59 -04:00
parent b1f4ff2641
commit 7925fc6caf
11 changed files with 693 additions and 641 deletions

View File

@@ -7,6 +7,7 @@ assignees: []
---
## User Story
**As a** (who wants to accomplish something)
**I want to** (what they want to accomplish)
**So that** (why they want to accomplish that thing)
@@ -15,29 +16,18 @@ assignees: []
### Scenario 1
Given ...
When ...
Then ...
Given ... When ... Then ...
### Scenario 2
Given ...
When ...
Then ...
Given ... When ... Then ...
### Scenario 3
Given ...
When ...
Then ...
Given ... When ... Then ...
## Subtasks
- [ ] Task 1
- [ ] Task 2
- [ ] Task 3
- [ ] Task 3

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,33 @@
# The Biergarten App
The Biergarten App is a multi-project monorepo with a .NET backend and an active React
Router frontend in `src/Website`. The current website focuses on account flows, theme
switching, shared UI components, Storybook coverage, and integration with the API.
The Biergarten App is a multi-project monorepo with a .NET backend and an active
React Router frontend in `src/Website`. The current website focuses on account
flows, theme switching, shared UI components, Storybook coverage, and
integration with the API.
## Documentation
- [Getting Started](docs/getting-started.md) - Local setup for backend and active website
- [Architecture](docs/architecture.md) - Current backend and frontend architecture
- [Docker Guide](docs/docker.md) - Container-based backend development and testing
- [Getting Started](docs/getting-started.md) - Local setup for backend and
active website
- [Architecture](docs/architecture.md) - Current backend and frontend
architecture
- [Docker Guide](docs/docker.md) - Container-based backend development and
testing
- [Testing](docs/testing.md) - Backend and frontend test commands
- [Environment Variables](docs/environment-variables.md) - Active configuration reference
- [Environment Variables](docs/environment-variables.md) - Active configuration
reference
- [Token Validation](docs/token-validation.md) - JWT validation architecture
- [Legacy Website Archive](docs/archive/legacy-website-v1.md) - Archived notes for the old Next.js frontend
- [Legacy Website Archive](docs/archive/legacy-website-v1.md) - Archived notes
for the old Next.js frontend
## Diagrams
- [Architecture](docs/diagrams-out/architecture.svg) - Layered architecture
- [Deployment](docs/diagrams-out/deployment.svg) - Docker topology
- [Authentication Flow](docs/diagrams-out/authentication-flow.svg) - Auth sequence
- [Database Schema](docs/diagrams-out/database-schema.svg) - Entity relationships
- [Authentication Flow](docs/diagrams-out/authentication-flow.svg) - Auth
sequence
- [Database Schema](docs/diagrams-out/database-schema.svg) - Entity
relationships
## Current Status
@@ -34,7 +42,8 @@ Active areas in the repository:
Legacy area retained for reference:
- `src/Website-v1` contains the archived Next.js frontend and is no longer the active website
- `src/Website-v1` contains the archived Next.js frontend and is no longer the
active website
## Tech Stack
@@ -43,7 +52,8 @@ Legacy area retained for reference:
- **UI Documentation**: Storybook 10, Vitest browser mode, Playwright
- **Testing**: xUnit, Reqnroll (BDD), FluentAssertions, Moq
- **Infrastructure**: Docker, Docker Compose
- **Security**: Argon2id password hashing, JWT access/refresh/confirmation tokens
- **Security**: Argon2id password hashing, JWT access/refresh/confirmation
tokens
## Quick Start
@@ -133,7 +143,8 @@ See [Testing](docs/testing.md) for the full command list.
Common active variables:
- Backend: `DB_SERVER`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `ACCESS_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `CONFIRMATION_TOKEN_SECRET`
- Backend: `DB_SERVER`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`,
`ACCESS_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `CONFIRMATION_TOKEN_SECRET`
- Frontend: `API_BASE_URL`, `SESSION_SECRET`, `NODE_ENV`
See [Environment Variables](docs/environment-variables.md) for details.
@@ -148,10 +159,13 @@ See [Environment Variables](docs/environment-variables.md) for details.
### Development Workflow
1. Start development environment: `docker compose -f docker-compose.dev.yaml up -d`
1. Start development environment:
`docker compose -f docker-compose.dev.yaml up -d`
2. Make changes to code
3. Run tests: `docker compose -f docker-compose.test.yaml up --abort-on-container-exit`
4. Rebuild if needed: `docker compose -f docker-compose.dev.yaml up -d --build api.core`
3. Run tests:
`docker compose -f docker-compose.test.yaml up --abort-on-container-exit`
4. Rebuild if needed:
`docker compose -f docker-compose.dev.yaml up -d --build api.core`
## Support

View File

@@ -4,24 +4,28 @@ This document describes the active architecture of The Biergarten App.
## High-Level Overview
The Biergarten App is a monorepo with a clear split between the backend and the active
website:
The Biergarten App is a monorepo with a clear split between the backend and the
active website:
- **Backend**: .NET 10 Web API with SQL Server and a layered architecture
- **Frontend**: React 19 + React Router 7 website in `src/Website`
- **Architecture Style**: Layered backend plus server-rendered React frontend
The legacy Next.js frontend has been retained in `src/Website-v1` for reference only and is
documented in [archive/legacy-website-v1.md](archive/legacy-website-v1.md).
The legacy Next.js frontend has been retained in `src/Website-v1` for reference
only and is documented in
[archive/legacy-website-v1.md](archive/legacy-website-v1.md).
## Diagrams
For visual representations, see:
- [architecture.svg](diagrams-out/architecture.svg) - Layered architecture diagram
- [architecture.svg](diagrams-out/architecture.svg) - Layered architecture
diagram
- [deployment.svg](diagrams-out/deployment.svg) - Docker deployment diagram
- [authentication-flow.svg](diagrams-out/authentication-flow.svg) - Authentication workflow
- [database-schema.svg](diagrams-out/database-schema.svg) - Database relationships
- [authentication-flow.svg](diagrams-out/authentication-flow.svg) -
Authentication workflow
- [database-schema.svg](diagrams-out/database-schema.svg) - Database
relationships
## Backend Architecture
@@ -218,7 +222,8 @@ public interface IAuthRepository
### Active Website (`src/Website`)
The current website is a React Router 7 application with server-side rendering enabled.
The current website is a React Router 7 application with server-side rendering
enabled.
```text
src/Website/
@@ -244,20 +249,22 @@ src/Website/
### Theme System
The active website uses semantic DaisyUI theme tokens backed by four Biergarten themes:
The active website uses semantic DaisyUI theme tokens backed by four Biergarten
themes:
- Biergarten Lager
- Biergarten Stout
- Biergarten Cassis
- Biergarten Weizen
All component styling should prefer semantic tokens such as `primary`, `success`,
`surface`, and `highlight` instead of hard-coded color values.
All component styling should prefer semantic tokens such as `primary`,
`success`, `surface`, and `highlight` instead of hard-coded color values.
### Legacy Frontend
The previous Next.js frontend has been archived at `src/Website-v1`. Active product and
engineering documentation should point to `src/Website`, while legacy notes live in
The previous Next.js frontend has been archived at `src/Website-v1`. Active
product and engineering documentation should point to `src/Website`, while
legacy notes live in
[archive/legacy-website-v1.md](archive/legacy-website-v1.md).
## Security Architecture
@@ -387,8 +394,8 @@ For details, see [Docker Guide](docker.md).
### Health Checks
**SQL Server**: Validates database connectivity **API**: Checks service health and
dependencies
**SQL Server**: Validates database connectivity **API**: Checks service health
and dependencies
**Configuration**:

View File

@@ -1,7 +1,7 @@
# Docker Guide
This document covers Docker deployment, configuration, and troubleshooting for The
Biergarten App.
This document covers Docker deployment, configuration, and troubleshooting for
The Biergarten App.
## Overview
@@ -13,7 +13,8 @@ The project uses Docker Compose to orchestrate multiple services:
- .NET API
- Test runners
See the [deployment diagram](diagrams/pdf/deployment.pdf) for visual representation.
See the [deployment diagram](diagrams/pdf/deployment.pdf) for visual
representation.
## Docker Compose Environments
@@ -144,7 +145,11 @@ api.core / tests (start when ready)
```yaml
healthcheck:
test: ['CMD-SHELL', "sqlcmd -S localhost -U sa -P '${DB_PASSWORD}' -C -Q 'SELECT 1'"]
test:
[
"CMD-SHELL",
"sqlcmd -S localhost -U sa -P '${DB_PASSWORD}' -C -Q 'SELECT 1'",
]
interval: 10s
timeout: 5s
retries: 12
@@ -209,16 +214,16 @@ Each environment uses isolated bridge networks:
All containers are configured via environment variables from `.env` files:
```yaml
env_file: '.env.dev' # or .env.test, .env.prod
env_file: ".env.dev" # or .env.test, .env.prod
environment:
ASPNETCORE_ENVIRONMENT: 'Development'
DOTNET_RUNNING_IN_CONTAINER: 'true'
DB_SERVER: '${DB_SERVER}'
DB_NAME: '${DB_NAME}'
DB_USER: '${DB_USER}'
DB_PASSWORD: '${DB_PASSWORD}'
JWT_SECRET: '${JWT_SECRET}'
ASPNETCORE_ENVIRONMENT: "Development"
DOTNET_RUNNING_IN_CONTAINER: "true"
DB_SERVER: "${DB_SERVER}"
DB_NAME: "${DB_NAME}"
DB_USER: "${DB_USER}"
DB_PASSWORD: "${DB_PASSWORD}"
JWT_SECRET: "${JWT_SECRET}"
```
For complete list, see [Environment Variables](environment-variables.md).

View File

@@ -1,7 +1,7 @@
# Environment Variables
This document covers the active environment variables used by the current Biergarten
stack.
This document covers the active environment variables used by the current
Biergarten stack.
## Overview
@@ -19,8 +19,8 @@ Direct environment variable access via `Environment.GetEnvironmentVariable()`.
### Frontend (`src/Website`)
The active website reads runtime values from the server environment for its auth and API
integration.
The active website reads runtime values from the server environment for its auth
and API integration.
### Docker
@@ -54,14 +54,15 @@ Provide complete connection string:
DB_CONNECTION_STRING="Server=localhost,1433;Database=Biergarten;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;"
```
**Priority**: `DB_CONNECTION_STRING` is checked first. If not found, connection string is
built from components.
**Priority**: `DB_CONNECTION_STRING` is checked first. If not found, connection
string is built from components.
**Implementation**: See `DefaultSqlConnectionFactory.cs`
### JWT Authentication Secrets (Backend)
The backend uses separate secrets for different token types to enable independent key rotation and validation isolation.
The backend uses separate secrets for different token types to enable
independent key rotation and validation isolation.
```bash
# Access token secret (1-hour tokens)
@@ -131,8 +132,8 @@ DOTNET_RUNNING_IN_CONTAINER=true # Flag for container execution
## Frontend Variables (`src/Website`)
The active website does not use the old Next.js/Prisma environment model. Its core runtime
variables are:
The active website does not use the old Next.js/Prisma environment model. Its
core runtime variables are:
```bash
API_BASE_URL=http://localhost:8080 # Base URL for the .NET API
@@ -208,9 +209,10 @@ cp .env.example .env.dev
## Legacy Frontend Variables
Variables for the archived Next.js frontend (`src/Website-v1`) have been removed from this
active reference. See [archive/legacy-website-v1.md](archive/legacy-website-v1.md) if you
need the legacy Prisma, Cloudinary, Mapbox, or SparkPost notes.
Variables for the archived Next.js frontend (`src/Website-v1`) have been removed
from this active reference. See
[archive/legacy-website-v1.md](archive/legacy-website-v1.md) if you need the
legacy Prisma, Cloudinary, Mapbox, or SparkPost notes.
**Docker Compose Mapping**:
@@ -243,8 +245,8 @@ need the legacy Prisma, Cloudinary, Mapbox, or SparkPost notes.
| `MSSQL_PID` | | | ✓ | No | SQL Server edition |
| `DOTNET_RUNNING_IN_CONTAINER` | ✓ | | ✓ | No | Container flag |
\* Either `DB_CONNECTION_STRING` OR the component variables (`DB_SERVER`, `DB_NAME`,
`DB_USER`, `DB_PASSWORD`) must be provided.
\* Either `DB_CONNECTION_STRING` OR the component variables (`DB_SERVER`,
`DB_NAME`, `DB_USER`, `DB_PASSWORD`) must be provided.
## Validation
@@ -258,8 +260,8 @@ Variables are validated at startup:
### Frontend Validation
The active website relies on runtime defaults for local development and the surrounding
server environment in deployed environments.
The active website relies on runtime defaults for local development and the
surrounding server environment in deployed environments.
- `API_BASE_URL` defaults to `http://localhost:8080`
- `SESSION_SECRET` falls back to a development-only local secret

View File

@@ -1,7 +1,7 @@
# Getting Started
This guide covers local setup for the current Biergarten stack: the .NET backend in
`src/Core` and the active React Router frontend in `src/Website`.
This guide covers local setup for the current Biergarten stack: the .NET backend
in `src/Core` and the active React Router frontend in `src/Website`.
## Prerequisites
@@ -128,8 +128,9 @@ dotnet run --project API/API.Core/API.Core.csproj
## Legacy Frontend Note
The previous Next.js frontend now lives in `src/Website-v1` and is not the active website.
Legacy setup details have been moved to [docs/archive/legacy-website-v1.md](archive/legacy-website-v1.md).
The previous Next.js frontend now lives in `src/Website-v1` and is not the
active website. Legacy setup details have been moved to
[docs/archive/legacy-website-v1.md](archive/legacy-website-v1.md).
## Next Steps

View File

@@ -1,6 +1,7 @@
# Testing
This document describes the testing strategy and how to run tests for The Biergarten App.
This document describes the testing strategy and how to run tests for The
Biergarten App.
## Overview
@@ -9,13 +10,15 @@ The project uses a multi-layered testing approach across backend and frontend:
- **API.Specs** - BDD integration tests using Reqnroll (Gherkin)
- **Infrastructure.Repository.Tests** - Unit tests for data access layer
- **Service.Auth.Tests** - Unit tests for authentication business logic
- **Storybook Vitest project** - Browser-based interaction tests for shared website stories
- **Storybook Playwright suite** - Browser checks against Storybook-rendered components
- **Storybook Vitest project** - Browser-based interaction tests for shared
website stories
- **Storybook Playwright suite** - Browser checks against Storybook-rendered
components
## Running Tests with Docker (Recommended)
The easiest way to run all tests is using Docker Compose, which sets up an isolated test
environment:
The easiest way to run all tests is using Docker Compose, which sets up an
isolated test environment:
```bash
docker compose -f docker-compose.test.yaml up --abort-on-container-exit
@@ -98,7 +101,8 @@ npm run test:storybook
**Purpose**:
- Verifies shared stories such as form fields, submit buttons, navbar states, toasts, and the theme gallery
- Verifies shared stories such as form fields, submit buttons, navbar states,
toasts, and the theme gallery
- Runs in browser mode via Vitest and Storybook integration
### Frontend Playwright Storybook Tests
@@ -113,7 +117,8 @@ npm run test:storybook:playwright
- Storybook dependencies installed
- Playwright browser dependencies installed
- The command will start or reuse the Storybook server defined in `playwright.storybook.config.ts`
- The command will start or reuse the Storybook server defined in
`playwright.storybook.config.ts`
## Test Coverage
@@ -278,7 +283,8 @@ Scenario: User login with valid credentials
## Continuous Integration
Tests run automatically in CI/CD pipelines using the test Docker Compose configuration:
Tests run automatically in CI/CD pipelines using the test Docker Compose
configuration:
```bash
# CI/CD command
@@ -292,7 +298,8 @@ Exit codes:
- `0` - All tests passed
- Non-zero - Test failures occurred
Frontend UI checks should also be included in CI for the active website workspace:
Frontend UI checks should also be included in CI for the active website
workspace:
```bash
cd src/Website

View File

@@ -2,11 +2,14 @@
## Overview
The Core project implements comprehensive JWT token validation across three token types:
The Core project implements comprehensive JWT token validation across three
token types:
- **Access Tokens**: Short-lived (1 hour) tokens for API authentication
- **Refresh Tokens**: Long-lived (21 days) tokens for obtaining new access tokens
- **Confirmation Tokens**: Short-lived (30 minutes) tokens for email confirmation
- **Refresh Tokens**: Long-lived (21 days) tokens for obtaining new access
tokens
- **Confirmation Tokens**: Short-lived (30 minutes) tokens for email
confirmation
## Components
@@ -17,10 +20,13 @@ The Core project implements comprehensive JWT token validation across three toke
Low-level JWT operations.
**Methods:**
- `GenerateJwt()` - Creates signed JWT tokens
- `ValidateJwtAsync()` - Validates token signature, expiration, and format
**Implementation:** [JwtInfrastructure.cs](Infrastructure.Jwt/JwtInfrastructure.cs)
**Implementation:**
[JwtInfrastructure.cs](Infrastructure.Jwt/JwtInfrastructure.cs)
- Uses Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler
- Algorithm: HS256 (HMAC-SHA256)
- Validates token lifetime, signature, and well-formedness
@@ -32,16 +38,20 @@ Low-level JWT operations.
High-level token validation with context (token type, user extraction).
**Methods:**
- `ValidateAccessTokenAsync(string token)` - Validates access tokens
- `ValidateRefreshTokenAsync(string token)` - Validates refresh tokens
- `ValidateConfirmationTokenAsync(string token)` - Validates confirmation tokens
**Returns:** `ValidatedToken` record containing:
- `UserId` (Guid)
- `Username` (string)
- `Principal` (ClaimsPrincipal) - Full JWT claims
**Implementation:** [TokenValidationService.cs](Service.Auth/TokenValidationService.cs)
**Implementation:**
[TokenValidationService.cs](Service.Auth/TokenValidationService.cs)
- Reads token secrets from environment variables
- Extracts and validates claims (Sub, UniqueName)
- Throws `UnauthorizedException` on validation failure
@@ -51,15 +61,18 @@ High-level token validation with context (token type, user extraction).
Token generation (existing service extended).
**Methods:**
- `GenerateAccessToken(UserAccount)` - Creates 1-hour access token
- `GenerateRefreshToken(UserAccount)` - Creates 21-day refresh token
- `GenerateConfirmationToken(UserAccount)` - Creates 30-minute confirmation token
- `GenerateConfirmationToken(UserAccount)` - Creates 30-minute confirmation
token
### Integration Points
#### [ConfirmationService](Service.Auth/IConfirmationService.cs)
**Flow:**
1. Receives confirmation token from user
2. Calls `TokenValidationService.ValidateConfirmationTokenAsync()`
3. Extracts user ID from validated token
@@ -69,6 +82,7 @@ Token generation (existing service extended).
#### [RefreshTokenService](Service.Auth/RefreshTokenService.cs)
**Flow:**
1. Receives refresh token from user
2. Calls `TokenValidationService.ValidateRefreshTokenAsync()`
3. Retrieves user account via `AuthRepository.GetUserByIdAsync()`
@@ -78,6 +92,7 @@ Token generation (existing service extended).
#### [AuthController](API.Core/Controllers/AuthController.cs)
**Endpoints:**
- `POST /api/auth/register` - Register new user
- `POST /api/auth/login` - Authenticate user
- `POST /api/auth/confirm?token=...` - Confirm email
@@ -88,11 +103,13 @@ Token generation (existing service extended).
### Token Secrets
Three independent secrets enable:
- **Key rotation** - Rotate each secret type independently
- **Isolation** - Compromise of one secret doesn't affect others
- **Different expiration** - Different token types can expire at different rates
**Environment Variables:**
```bash
ACCESS_TOKEN_SECRET=... # Signs 1-hour access tokens
REFRESH_TOKEN_SECRET=... # Signs 21-day refresh tokens
@@ -111,6 +128,7 @@ Each token is validated for:
### Error Handling
Validation failures return HTTP 401 Unauthorized:
- Invalid signature → "Invalid token"
- Expired token → "Invalid token" (message doesn't reveal reason for security)
- Missing claims → "Invalid token"
@@ -149,16 +167,19 @@ Validation failures return HTTP 401 Unauthorized:
### Unit Tests
**TokenValidationService.test.cs**
- Happy path: Valid token extraction
- Error cases: Invalid, expired, malformed tokens
- Missing/invalid claims scenarios
**RefreshTokenService.test.cs**
- Successful refresh with valid token
- Invalid/expired refresh token rejection
- Non-existent user handling
**ConfirmationService.test.cs**
- Successful confirmation with valid token
- Token validation failures
- User not found scenarios
@@ -166,16 +187,19 @@ Validation failures return HTTP 401 Unauthorized:
### BDD Tests (Reqnroll)
**TokenRefresh.feature**
- Successful token refresh
- Invalid/expired token rejection
- Missing token validation
**Confirmation.feature**
- Successful email confirmation
- Expired/tampered token rejection
- Missing token validation
**AccessTokenValidation.feature**
- Protected endpoint access token validation
- Invalid/expired access token rejection
- Token type mismatch (refresh used as access token)

View File

@@ -52,12 +52,12 @@ artificial intelligence is incapable of.**
## Model Bias and Language Quality
The underlying model's training biases surface within this pipeline.
Output quality tracks with how well a language is represented in the training
corpus: standard French (`fr-FR`) produces coherent text; regional variants like
`fr-CD` and `fr-CI` are noticeably weaker; low-resource languages like Welsh,
Māori, and Sicilian produce output that is syntactically plausible but often
semantically broken.
The underlying model's training biases surface within this pipeline. Output
quality tracks with how well a language is represented in the training corpus:
standard French (`fr-FR`) produces coherent text; regional variants like `fr-CD`
and `fr-CI` are noticeably weaker; low-resource languages like Welsh, Māori, and
Sicilian produce output that is syntactically plausible but often semantically
broken.
This is a property of the training distribution, not something that can be
mitigated through prompt design. This is a well-documented characteristic of
@@ -235,7 +235,9 @@ idiomatic phrasing.
]
```
This dataset, when fed into the pipeline will often times reason that a local variant of French is needed, but will often times just default to a standardized dialect of French, devoid of any cultural or linguistic nuance.
This dataset, when fed into the pipeline will often times reason that a local
variant of French is needed, but will often times just default to a standardized
dialect of French, devoid of any cultural or linguistic nuance.
For languages such as Welsh (Wales), Māori (Aotearoa/New Zealand), or Sicilian
(Sicily, Italy), the model can generate text that looks syntactically plausible
@@ -260,7 +262,13 @@ Output sample:
## Footnotes
[^llm-choke]: CHOKE (Certain Hallucinations Overriding Known Evidence) is a hallucination failure mode defined by Simhi et al. (2025), in which a model that can consistently answer a question correctly produces a confident, wrong response when the prompt is trivially perturbed. Source: Trust Me, I'm Wrong: LLMs Hallucinate with Certainty Despite Knowing the Answer — Adi Simhi, Itay Itzhak, Fazl Barez, Gabriel Stanovsky, Yonatan Belinkov.
[^llm-choke]:
CHOKE (Certain Hallucinations Overriding Known Evidence) is a hallucination
failure mode defined by Simhi et al. (2025), in which a model that can
consistently answer a question correctly produces a confident, wrong
response when the prompt is trivially perturbed. Source: Trust Me, I'm
Wrong: LLMs Hallucinate with Certainty Despite Knowing the Answer — Adi
Simhi, Itay Itzhak, Fazl Barez, Gabriel Stanovsky, Yonatan Belinkov.
[^llm-bias]:
e.g., Blasi et al. (2022), "Systematic Inequalities in Language Technology
@@ -279,8 +287,12 @@ Output sample:
[creativecommons.org/licenses/by-sa/4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en).
[^diacetyl-source]:
White Labs confirms that diacetyl is a yeast-derived fermentation byproduct: specifically, a compound produced during amino acid metabolism that leaks out of the yeast cell and oxidises into its characteristic buttery off-flavour. It is generally considered undesirable at any perceived level in most styles, though low levels are tolerated in some English ales and European lagers.
Source:
White Labs confirms that diacetyl is a yeast-derived fermentation byproduct:
specifically, a compound produced during amino acid metabolism that leaks
out of the yeast cell and oxidises into its characteristic buttery
off-flavour. It is generally considered undesirable at any perceived level
in most styles, though low levels are tolerated in some English ales and
European lagers. Source:
[whitelabs.com — Compound Spotlight: Diacetyl](https://www.whitelabs.com/news-update-detail?id=54).
[^diacetyl-rest]:

View File

@@ -1,8 +1,13 @@
# FULL SYSTEM PROMPT
You are an expert brewery copywriter, an architectural observer, and a master of zymurgy.
You are an expert brewery copywriter, an architectural observer, and a master of
zymurgy.
Your main goal is to come up with a fake, contextually accurate name and a matching description for a craft brewery located in a specific city. You need to base this on the exact geographic and cultural info provided. You also need to seamlessly blend historical background, cultural details, and highly specialized brewing methods to create a realistic and interesting story.
Your main goal is to come up with a fake, contextually accurate name and a
matching description for a craft brewery located in a specific city. You need to
base this on the exact geographic and cultural info provided. You also need to
seamlessly blend historical background, cultural details, and highly specialized
brewing methods to create a realistic and interesting story.
You will receive the inputs like this:
@@ -24,17 +29,28 @@ You will receive the inputs like this:
## CRITICAL OUTPUT FORMAT (READ CAREFULLY):
ABSOLUTELY NO MARKDOWN FORMATTING. Do NOT wrap your response in json or ``` blocks.
ABSOLUTELY NO MARKDOWN FORMATTING. Do NOT wrap your response in json or ```
blocks.
Do not add markdown, code fences, or postscript around the final JSON object. Do not say "Here is the JSON" or "Enjoy!".
Do not add markdown, code fences, or postscript around the final JSON object. Do
not say "Here is the JSON" or "Enjoy!".
The JSON must contain exactly four keys ("name_en", "description_en", "name_local", "description_local") in that order. Do not rename or add any other keys.
The JSON must contain exactly four keys ("name_en", "description_en",
"name_local", "description_local") in that order. Do not rename or add any other
keys.
ESCAPE ALL QUOTES inside all description fields using \", or use single quotes (' ') instead. This applies equally to description_en and description_local. If the local language uses non-standard quotation marks (such as guillemets or corner brackets), write them as literal Unicode characters rather than escaped HTML entities, and do not nest them inside double quotes without escaping.
ESCAPE ALL QUOTES inside all description fields using \", or use single quotes
(' ') instead. This applies equally to description_en and description_local. If
the local language uses non-standard quotation marks (such as guillemets or
corner brackets), write them as literal Unicode characters rather than escaped
HTML entities, and do not nest them inside double quotes without escaping.
DO NOT use actual line breaks (\n) inside any string. Keep all descriptions as one continuous string each.
DO NOT use actual line breaks (\n) inside any string. Keep all descriptions as
one continuous string each.
The description_en and description_local must each be between 225 and 300 words. Do not pad with repetition or summary, every sentence must earn its place. Be concise and specific.
The description_en and description_local must each be between 225 and 300 words.
Do not pad with repetition or summary, every sentence must earn its place. Be
concise and specific.
Expected JSON format:
@@ -51,38 +67,64 @@ Expected JSON format:
### THE HOOK:
The first sentence must be a sensory environmental hook written as a personal observation, something the owner notices or has always noticed. It should establish the local weather, smell, or soundscape of the city. Do not open with the brewery's name or a generic welcome.
The first sentence must be a sensory environmental hook written as a personal
observation, something the owner notices or has always noticed. It should
establish the local weather, smell, or soundscape of the city. Do not open with
the brewery's name or a generic welcome.
### GEOGRAPHIC & CULTURAL ANCHOR:
The story must be deeply tied to the provided geographic and cultural info. Weave in one or two specific historical or cultural details that ground the brewery in its place, enough to feel local, not so much that it reads like a history lesson.
The story must be deeply tied to the provided geographic and cultural info.
Weave in one or two specific historical or cultural details that ground the
brewery in its place, enough to feel local, not so much that it reads like a
history lesson.
### TECHNICAL BREWING DETAIL (VARY THIS!):
You must include one highly specialized technical brewing detail. To avoid sounding repetitive, make sure this varies a lot. Some examples: using local wild yeast (like spontaneous Brettanomyces), adjusting the water profile (like Burtonization), specific mashing techniques, or using local barrels for aging. Don't use basic concepts like generic mash temperatures.
You must include one highly specialized technical brewing detail. To avoid
sounding repetitive, make sure this varies a lot. Some examples: using local
wild yeast (like spontaneous Brettanomyces), adjusting the water profile (like
Burtonization), specific mashing techniques, or using local barrels for aging.
Don't use basic concepts like generic mash temperatures.
### ARCHITECTURAL DETAIL (VARY THIS!):
You must include one specific architectural or environmental detail, highlighting the building's physical wear, structure, or history. The owner should describe it with personal familiarity, something they've lived with long enough to stop noticing, then started noticing again. Avoid overused industry clichés like repurposed dairy equipment or glycol chillers.
You must include one specific architectural or environmental detail,
highlighting the building's physical wear, structure, or history. The owner
should describe it with personal familiarity, something they've lived with long
enough to stop noticing, then started noticing again. Avoid overused industry
clichés like repurposed dairy equipment or glycol chillers.
### THE INVITATION:
The last sentence must be a personal, low-key invitation from the owner, specific about place, not generic about the experience. The owner should point somewhere concrete rather than issuing a formal welcome. Avoid clichés like "come find us," "stop by anytime," "grab a stool," or "ask the bartender."
The last sentence must be a personal, low-key invitation from the owner,
specific about place, not generic about the experience. The owner should point
somewhere concrete rather than issuing a formal welcome. Avoid clichés like
"come find us," "stop by anytime," "grab a stool," or "ask the bartender."
### LOCAL LANGUAGE VERSION:
name_local is a direct translation of name_en into the local language or script.
Use the supplied local language codes to choose the language or script, and do not invent a language that is not listed.
Use the supplied local language codes to choose the language or script, and do
not invent a language that is not listed.
description_local carries the same content and structure as description_en but should read as though written by an owner who assumes their reader shares the local cultural context, references that needed explaining in English can be stated plainly, and phrasing should reflect natural idiom in that language rather than translated English sentence structure.
description_local carries the same content and structure as description_en but
should read as though written by an owner who assumes their reader shares the
local cultural context, references that needed explaining in English can be
stated plainly, and phrasing should reflect natural idiom in that language
rather than translated English sentence structure.
The length and anti-AI-pattern requirements apply equally to description_local.
The register of description_local should match the local variant of the language appropriate to the city, québécois French for Montréal, Belgian French for Brussels, castilian Spanish for Madrid, rioplatense Spanish for Buenos Aires, and so on.
The register of description_local should match the local variant of the language
appropriate to the city, québécois French for Montréal, Belgian French for
Brussels, castilian Spanish for Madrid, rioplatense Spanish for Buenos Aires,
and so on.
### THE BLOCKLIST (FORBIDDEN CONCEPTS):
You absolutely cannot use the following words and phrases. Make sure your final output doesn't have any of these:
You absolutely cannot use the following words and phrases. Make sure your final
output doesn't have any of these:
- "hidden gem"
- "passion"
@@ -99,15 +141,27 @@ You absolutely cannot use the following words and phrases. Make sure your final
#### FORBIDDEN WRITING PATTERNS
The following patterns are common AI writing pitfalls and must not appear in either description:
The following patterns are common AI writing pitfalls and must not appear in
either description:
- Negative parallelism constructions: "It's not X, it's Y" or "We're not about X, we're about Y"
- Inflated significance phrases: "stands as a testament," "plays a vital role," "leaves a lasting impact," "watershed moment," "deeply rooted," "rich cultural heritage," "rich cultural tapestry," "enduring legacy"
- Superficial trailing analyses: sentences ending in -ing words that add opinion without content ("ensuring consistency," "reflecting the city's spirit," "highlighting our commitment")
- Promotional travel-copy tone: "breathtaking," "must-visit," "stunning," "vibrant"
- Overused conjunctive transitions used as sentence openers: "Moreover," "Furthermore," "In addition," "In contrast"
- Negative parallelism constructions: "It's not X, it's Y" or "We're not about
X, we're about Y"
- Inflated significance phrases: "stands as a testament," "plays a vital role,"
"leaves a lasting impact," "watershed moment," "deeply rooted," "rich cultural
heritage," "rich cultural tapestry," "enduring legacy"
- Superficial trailing analyses: sentences ending in -ing words that add opinion
without content ("ensuring consistency," "reflecting the city's spirit,"
"highlighting our commitment")
- Promotional travel-copy tone: "breathtaking," "must-visit," "stunning,"
"vibrant"
- Overused conjunctive transitions used as sentence openers: "Moreover,"
"Furthermore," "In addition," "In contrast"
- Rule of three: do not consistently organise ideas or examples in triplets
### VOICE & PERSPECTIVE:
The description must be written in the first person, from the perspective of the brewery's owner. Favour "we" and "our" over "I" and "my." The owner may use "I" sparingly for personal observations that only they could make, but the default register should be collective. The tone should feel lived-in and a little weathered. Do not use third-person or second-person pronouns.
The description must be written in the first person, from the perspective of the
brewery's owner. Favour "we" and "our" over "I" and "my." The owner may use "I"
sparingly for personal observations that only they could make, but the default
register should be collective. The tone should feel lived-in and a little
weathered. Do not use third-person or second-person pronouns.