Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Pull Request
name: Pull Request
on:
pull_request:
paths-ignore:
Expand All @@ -8,14 +8,44 @@ jobs:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Setup dotnet
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x
dotnet-version: 10.0.x
- name: Install Tools
run: dotnet tool restore
working-directory: ./fsharp-view-engine
- name: Install Packages
run: dotnet paket install
working-directory: ./fsharp-view-engine
- name: Test
run: ./fake.sh Test
working-directory: ./fsharp-view-engine
preview:
name: Preview
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
needs:
- test
steps:
- uses: actions/checkout@v5
- name: Setup Node
uses: actions/setup-node@v5
with:
node-version: 24
- name: Install Packages
run: npm install
working-directory: ./pulumi
- name: Preview
uses: pulumi/actions@v6
with:
work-dir: ./pulumi
command: preview
stack-name: prod
diff: true
comment-on-pr: true
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_TOKEN }}
33 changes: 29 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Release
name: Release
on:
release:
branches:
Expand All @@ -10,16 +10,41 @@ jobs:
name: Publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Setup dotnet
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x
dotnet-version: 10.0.x
- name: Restore Tools
run: dotnet tool restore
working-directory: ./fsharp-view-engine
- name: Install Packages
run: dotnet paket install
working-directory: ./fsharp-view-engine
- name: Publish
run: ./fake.sh Publish
working-directory: ./fsharp-view-engine
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
deploy:
name: Deploy
runs-on: ubuntu-latest
needs:
- publish
steps:
- uses: actions/checkout@v5
- name: Setup Node
uses: actions/setup-node@v5
with:
node-version: 24
- name: Install Packages
run: npm install
working-directory: ./pulumi
- name: Update
uses: pulumi/actions@v6
with:
work-dir: ./pulumi
command: up
stack-name: prod
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_TOKEN }}
9 changes: 1 addition & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
.idea
.vscode
.paket
obj
bin
nugets
paket-files
*DotSettings.user
.claude
94 changes: 94 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview
FSharp.ViewEngine is a view engine for F# web applications. It provides a functional approach to generating HTML with type-safe F# code, including integrated support for HTMX, Alpine.js, Tailwind CSS, and SVG elements.

## Repository Structure
- `fsharp-view-engine/` — F# library, app, tests, and build system
- `pulumi/` — Pulumi infrastructure (TypeScript) for deploying the demo app
- `.github/` — CI workflows (at repo root, with `working-directory: fsharp-view-engine`)
- `.claude/` — Claude Code settings

## Common Development Commands

**Important:** When running in a bash shell (including Claude Code), always use `./fake.sh` instead of `fake.cmd`.

```bash
# All dotnet/fake commands run from fsharp-view-engine/
cd fsharp-view-engine

# Restore tools and packages
dotnet tool restore
dotnet paket install

# Run tests (uses Expecto, so dotnet run, not dotnet test)
./fake.sh Test
dotnet run --project src/Tests/Tests.fsproj # Direct

# Run a single test by name
dotnet run --project src/Tests/Tests.fsproj -- --filter "Should render html document"

# Run demo app with Tailwind watch
./fake.sh Watch

# Create NuGet package (needs GITHUB_REF_NAME env var)
./fake.sh Pack

# Pulumi deployment
cd pulumi
npm install
pulumi up -s prod
```

## Architecture

### Core Type System (`fsharp-view-engine/src/FSharp.ViewEngine/Core.fs`)
Two discriminated unions form the foundation:
- **Element**: `Text | Tag | Void | Fragment | Raw | Noop` — represents DOM nodes
- **Attribute**: `KeyValue | Boolean | Children | Noop` — represents HTML attributes

Rendering uses `StringBuilder` with recursive pattern matching. `Text` is HTML-encoded; `Raw` is not. `Void` elements (e.g., `br`, `img`) are self-closing and reject children.

### Module Organization
- `Html.fs` — Standard HTML elements and attributes as static members on `Html` type
- `Htmx.fs` — HTMX attributes (`_hxGet`, `_hxPost`, etc.) on `Htmx` type
- `Alpine.fs` — Alpine.js directives (`_xData`, `_xShow`, etc.) on `Alpine` type
- `Tailwind.fs` — Tailwind UI custom elements on `Tailwind` type
- `Svg.fs` — SVG elements and attributes on `Svg` type

### Usage Pattern
```fsharp
open FSharp.ViewEngine
open type Html
open type Htmx

div [ _class "container"; _hxGet "/api"; _children [ h1 "Hello" ] ]
|> Element.render
```

### Project Structure
- `fsharp-view-engine/src/FSharp.ViewEngine/` — Core library (NuGet package)
- `fsharp-view-engine/src/Tests/` — xUnit tests
- `fsharp-view-engine/src/Build/` — FAKE build system (`Program.fs` defines targets)
- `fsharp-view-engine/src/App/` — Demo Giraffe web app with Markdown docs
- `pulumi/` — Infrastructure as code (Pulumi + TypeScript)

## Development Patterns

- **New HTML elements** in `Html.fs`: use `Tag` for normal elements, `Void` for self-closing. Add convenience overloads (e.g., `p (text:string)`).
- **Framework attributes**: HTMX → `Htmx.fs` with `_hx` prefix; Alpine → `Alpine.fs` with `_x` prefix.
- **Tests** compare rendered HTML strings using `String.clean` for whitespace normalization. Use `// language=HTML` comment for IDE syntax highlighting in expected strings.

## Build System
- FAKE build scripts invoked via `fake.cmd`/`fake.sh`
- Paket for package management (`paket.dependencies` at root of `fsharp-view-engine/`)
- .NET 10.0 SDK (`global.json`)
- CI: GitHub Actions — tests on PRs, NuGet publish on release tags (`v*.*.*`)

## Infrastructure (Pulumi)
- TypeScript Pulumi project in `pulumi/`
- Deploys demo app to Kubernetes via AWS ECR + Cloudflare Tunnel
- Domain: `fsharpviewengine.meiermade.com`
- Stack references: `identity`, `infrastructure`, `fsharp-view-engine-identity`
115 changes: 0 additions & 115 deletions WARP.md

This file was deleted.

6 changes: 0 additions & 6 deletions etc/logo.svg

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
"isRoot": true,
"tools": {
"paket": {
"version": "8.0.3",
"version": "10.3.1",
"commands": [
"paket"
]
],
"rollForward": false
}
}
}
8 changes: 8 additions & 0 deletions fsharp-view-engine/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
**/.git
**/.idea
**/.vs
**/bin
**/obj
**/nugets
**/nul
**/.claude
8 changes: 8 additions & 0 deletions fsharp-view-engine/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.idea
.vscode
.paket
obj
bin
nugets
paket-files
*DotSettings.user
1 change: 1 addition & 0 deletions fsharp-view-engine/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See [CLAUDE.md](CLAUDE.md) for project guidance.
Loading
Loading