diff --git a/BACKLOG.md b/BACKLOG.md
index 8ae015e..3011570 100644
--- a/BACKLOG.md
+++ b/BACKLOG.md
@@ -40,3 +40,13 @@ Each should be addressed one at a time, and the item should be removed after imp
- Header should probably always be attached to the top of the screen?
- Feed item "view details" button should go away, the "new brew" in "addded a new brew" should take to view page instead (underline this text)
+
+- Manage brews page truncates notes, but variables wrap to considerably more lines, probably don't need to truncate notes.
+
+- Loading on htmx could probably be snappier by using a loading bar, and waiting until everything is loaded
+ - Alternative could be using transitionary animations between skeleton and full loads
+ - Do we even need skeleton loading with SSR? (I think yes here because of PDS data fetches -- maybe not if we kept a copy of the data)
+
+- I think we should be using htmx more than alpine, we are leaning too much on alpine and not enough on htmx
+ - I think for transitions we should be able to swap to htmx from alpine?
+ - I may be completely off base on this though
diff --git a/TEMPL_POC.md b/TEMPL_POC.md
new file mode 100644
index 0000000..436ecb4
--- /dev/null
+++ b/TEMPL_POC.md
@@ -0,0 +1,202 @@
+# Templ POC - Phase 1 Complete ✅
+
+This document describes the Proof of Concept (POC) implementation of Templ for the Arabica coffee tracking application.
+
+## What Was Implemented
+
+### Phase 1: Setup & Learning (COMPLETED)
+
+We successfully completed the first phase of the Templ hybrid approach migration:
+
+1. **Development Environment Setup**
+ - Added `templ` to the Nix flake development shell (`flake.nix`)
+ - Added `github.com/a-h/templ` Go module dependency
+ - Templ CLI version: 0.3.960 (from Nix)
+ - Templ Go module: 0.3.977
+
+2. **Component Directory Structure**
+ - Created `internal/components/` directory
+ - Established pattern for Templ component organization
+
+3. **Converted Components** (4 total)
+ - **Layout** (`layout.templ`) - Base HTML layout with head, body structure
+ - **Header** (`header.templ`) - Navigation bar with Alpine.js dropdown
+ - **Footer** (`footer.templ`) - Site footer with links
+ - **About Page** (`about.templ`) - Full About page content
+
+4. **Handler Integration**
+ - Updated `internal/handlers/handlers.go` to import components
+ - Modified `HandleAbout` to use `components.About()` instead of `bff.RenderTemplate()`
+ - Successfully maintains Alpine.js functionality (user dropdown)
+
+5. **Testing**
+ - Created `about_test.go` with component tests
+ - All tests passing ✅
+ - Verified authenticated/unauthenticated states render correctly
+
+## Key Learnings
+
+### Templ Syntax Patterns
+
+1. **Conditional Rendering**
+
+ ```templ
+ if isAuthenticated {
+
Authenticated content
+ }
+ ```
+
+2. **String Interpolation**
+
+ ```templ
+ { userProfile.DisplayName } // Simple variable
+ { "@" + userProfile.Handle } // Concatenation (for literal @ symbol)
+ ```
+
+3. **Component Composition**
+
+ ```templ
+ templ About(data *LayoutData) {
+ @Layout(data, AboutContent(data.IsAuthenticated))
+ }
+ ```
+
+4. **Alpine.js Compatibility**
+ - Alpine.js directives (`x-data`, `x-show`, `@click`, etc.) work perfectly in Templ
+ - No changes needed to Alpine.js usage patterns
+
+### Build Process
+
+```bash
+# Generate Go code from .templ files
+nix develop -c templ generate
+
+# Build the application
+nix develop -c go build ./...
+
+# Run tests
+nix develop -c go test ./internal/components -v
+```
+
+## File Structure
+
+```
+internal/components/
+├── about.templ # About page component
+├── about_templ.go # Generated Go code (auto-generated)
+├── about_test.go # Component tests
+├── footer.templ # Footer component
+├── footer_templ.go # Generated Go code
+├── header.templ # Header/nav component
+├── header_templ.go # Generated Go code
+├── layout.templ # Base layout component
+└── layout_templ.go # Generated Go code
+```
+
+## What Works
+
+✅ **Type-safe templating** - Compile-time checks for data structures
+✅ **Component composition** - Layout wraps content components
+✅ **Alpine.js integration** - No conflicts, works seamlessly
+✅ **Conditional rendering** - Authentication state handled correctly
+✅ **IDE support** - Full Go autocomplete and type checking
+✅ **Testing** - Easy to test components in isolation
+✅ **Build integration** - Fits into existing Nix development workflow
+
+## What's Next (Future Phases)
+
+### Phase 2: Component Library
+
+- Create reusable UI components (buttons, cards, forms)
+- Convert common partials to components
+- Establish component naming conventions
+
+### Phase 3: Page Migration
+
+- Convert remaining pages (home, brews, manage, profile)
+- Maintain parity with existing templates
+- Ensure all HTMX interactions still work
+
+### Phase 4: JavaScript Simplification
+
+- Remove entity-manager.js (logic moves to server)
+- Remove dropdown-manager.js (Templ components handle this)
+- Keep Alpine.js for client-side state (modals, dynamic forms)
+
+### Phase 5: Cleanup & Documentation
+
+- Remove old .tmpl files
+- Update CLAUDE.md with Templ patterns
+- Add component documentation
+
+## Migration Considerations
+
+### What to Keep
+
+- **Alpine.js** - Essential for client-side state management
+ - Modal state (open/close without server round-trip)
+ - Dynamic form arrays (add/remove pours)
+ - Rating slider live feedback
+ - Form draft persistence to LocalStorage
+
+- **HTMX** - Server communication for partial updates
+ - Form submissions
+ - Content loading
+ - Target swapping
+
+- **ArabicaCache** - Client-side caching with TTL
+
+### What Can Be Simplified
+
+- **Template rendering logic** - Simpler with Templ components
+- **Entity management** - Can move some logic server-side
+- **Dropdown population** - Can be handled by Templ components
+
+## Performance Notes
+
+- Templ generates Go code at compile time (no runtime parsing)
+- Similar performance to `html/template` but with type safety
+- No impact on client-side JavaScript bundle size
+- Generated code is efficient and readable
+
+## Recommendations
+
+1. **Continue with hybrid approach** - Templ + Alpine.js + HTMX
+2. **Incremental migration** - One page at a time
+3. **Maintain Alpine.js** - Don't try to replace client-side state management
+4. **Focus on DX improvements** - Type safety and component reuse are the wins
+
+## Example Usage
+
+```go
+// Handler code (internal/handlers/handlers.go)
+func (h *Handler) HandleAbout(w http.ResponseWriter, r *http.Request) {
+ data := &components.LayoutData{
+ Title: "About",
+ IsAuthenticated: isAuthenticated,
+ UserDID: didStr,
+ UserProfile: userProfile,
+ }
+
+ // Render Templ component
+ if err := components.About(data).Render(r.Context(), w); err != nil {
+ http.Error(w, "Failed to render page", http.StatusInternalServerError)
+ }
+}
+```
+
+## Conclusion
+
+**POC Status: ✅ SUCCESS**
+
+The Templ POC demonstrates that:
+
+- Templ integrates smoothly with existing Go code
+- Alpine.js functionality is preserved
+- Type safety improves developer experience
+- Testing is straightforward
+- Build process is manageable with Nix
+
+**Recommendation: Proceed with incremental migration using the hybrid approach.**
+
+The evaluation's conclusion holds true: Templ is an excellent `html/template` replacement that provides better DX without compromising the user experience or requiring JavaScript elimination.
diff --git a/Caddyfile b/deploy/Caddyfile
similarity index 100%
rename from Caddyfile
rename to deploy/Caddyfile
diff --git a/Dockerfile b/deploy/Dockerfile
similarity index 100%
rename from Dockerfile
rename to deploy/Dockerfile
diff --git a/compose.yml b/deploy/compose.yml
similarity index 100%
rename from compose.yml
rename to deploy/compose.yml
diff --git a/flake.nix b/flake.nix
index 25a1e29..fbad747 100644
--- a/flake.nix
+++ b/flake.nix
@@ -8,7 +8,7 @@
(system: function nixpkgs.legacyPackages.${system} system);
in {
devShells = forAllSystems (pkgs: system: {
- default = pkgs.mkShell { packages = with pkgs; [ go tailwindcss ]; };
+ default = pkgs.mkShell { packages = with pkgs; [ go tailwindcss templ ]; };
});
packages = forAllSystems (pkgs: system: rec {
diff --git a/go.mod b/go.mod
index 2f61f48..9ec453f 100644
--- a/go.mod
+++ b/go.mod
@@ -3,9 +3,14 @@ module arabica
go 1.25.4
require (
+ github.com/a-h/templ v0.3.977
github.com/bluesky-social/indigo v0.0.0-20260106221649-6fcd9317e725
+ github.com/gorilla/websocket v1.5.3
+ github.com/klauspost/compress v1.18.3
+ github.com/ptdewey/shutter v0.1.4
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.10.0
+ github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4
go.etcd.io/bbolt v1.3.8
golang.org/x/sync v0.19.0
)
@@ -16,11 +21,8 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
- github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
- github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
- github.com/klauspost/compress v1.18.3 // indirect
github.com/kortschak/utter v1.7.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -31,12 +33,10 @@ require (
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
- github.com/ptdewey/shutter v0.1.4 // indirect
- github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 // indirect
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
- golang.org/x/crypto v0.21.0 // indirect
- golang.org/x/net v0.23.0 // indirect
+ golang.org/x/crypto v0.40.0 // indirect
+ golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
diff --git a/go.sum b/go.sum
index 5e7406e..8dea89e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg=
+github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bluesky-social/indigo v0.0.0-20260106221649-6fcd9317e725 h1:gfrLAhE6PHun4MDypO/5hpnaHPd9Dbe9+JxZL0gC4ic=
@@ -87,10 +89,10 @@ gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAF
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
-golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
-golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
+golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
+golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
+golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/internal/bff/__snapshots__/bean_with_roaster.snap b/internal/bff/__snapshots__/bean_with_roaster.snap
index bea28bd..b59d874 100644
--- a/internal/bff/__snapshots__/bean_with_roaster.snap
+++ b/internal/bff/__snapshots__/bean_with_roaster.snap
@@ -5,10 +5,10 @@ file_name: feed_template_snapshot_test.go
version: 0.1.0
---
\ No newline at end of file
diff --git a/internal/components/about.templ b/internal/components/about.templ
new file mode 100644
index 0000000..f0e8e57
--- /dev/null
+++ b/internal/components/about.templ
@@ -0,0 +1,110 @@
+package components
+
+templ AboutContent(isAuthenticated bool) {
+
+
+
+
About Arabica
+ ALPHA
+
+
+
+ Alpha Software: Arabica is currently in early development. Features may change, and data structures could be modified in future updates.
+
+
+
+
+
Your Coffee Journey, Your Data
+
+ Arabica is a coffee brew tracking application built on the AT Protocol, a decentralized social networking protocol.
+ Unlike traditional apps where your data is locked in a company's database, Arabica stores your brew logs,
+ coffee beans, and equipment information in your own Personal Data Server (PDS).
+
+
+
+
What Makes Arabica Different?
+
+
You own your data - All your brew logs live in your PDS, not our servers
+
Portable identity - Switch PDS providers anytime without losing your data
+
Privacy by design - You control who sees your brews
+
Open protocol - Built on the AT Protocol, the same technology powering Bluesky
+
+
+
+
Features
+
+
+
Track Your Brews
+
Log every detail: beans, grind size, water temp, brew time, and tasting notes
+
+
+
Manage Equipment
+
Keep track of your grinders, brewers, beans, and roasters
+
+
+
Community Feed
+
Share your best brews with the community (coming soon: likes and comments!)
+
+
+
Export Your Data
+
Export all your brews anytime in JSON format
+
+
+
+
+
The AT Protocol Advantage
+
+ The AT Protocol is a decentralized social networking protocol that gives users true ownership of their data
+ and identity. When you use Arabica:
+
+
+
Your brew data is stored as ATProto records in collections on your PDS
+
You authenticate via OAuth with your PDS, not with us
+
References between records (like linking a brew to a bean) use AT-URIs
+
Your identity is portable - change PDS providers without losing your account
+
+
+
+
Getting Started
+
+ To use Arabica, you'll need an account on a PDS that supports the AT Protocol.
+ The easiest way is to create a Bluesky account at bsky.app.
+
+
+ Once you have an account, simply log in with your handle (e.g., yourname.bsky.social) and start tracking your brews!
+
+
+
+
Open Source
+
+ Arabica is open source software. You can view the code, contribute, or even run your own instance.
+ Visit our GitHub repository to learn more.
+
+}
+
+// About is the full About page with layout
+templ About(data *LayoutData) {
+ @Layout(data, AboutContent(data.IsAuthenticated))
+}
diff --git a/internal/components/about_templ.go b/internal/components/about_templ.go
new file mode 100644
index 0000000..e87b211
--- /dev/null
+++ b/internal/components/about_templ.go
@@ -0,0 +1,85 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.960
+package components
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+func AboutContent(isAuthenticated bool) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
About Arabica
ALPHA
Alpha Software: Arabica is currently in early development. Features may change, and data structures could be modified in future updates.
Your Coffee Journey, Your Data
Arabica is a coffee brew tracking application built on the AT Protocol, a decentralized social networking protocol. Unlike traditional apps where your data is locked in a company's database, Arabica stores your brew logs, coffee beans, and equipment information in your own Personal Data Server (PDS).
What Makes Arabica Different?
You own your data - All your brew logs live in your PDS, not our servers
Portable identity - Switch PDS providers anytime without losing your data
Privacy by design - You control who sees your brews
Open protocol - Built on the AT Protocol, the same technology powering Bluesky
Features
Track Your Brews
Log every detail: beans, grind size, water temp, brew time, and tasting notes
Manage Equipment
Keep track of your grinders, brewers, beans, and roasters
Community Feed
Share your best brews with the community (coming soon: likes and comments!)
Export Your Data
Export all your brews anytime in JSON format
The AT Protocol Advantage
The AT Protocol is a decentralized social networking protocol that gives users true ownership of their data and identity. When you use Arabica:
Your brew data is stored as ATProto records in collections on your PDS
You authenticate via OAuth with your PDS, not with us
References between records (like linking a brew to a bean) use AT-URIs
Your identity is portable - change PDS providers without losing your account
Getting Started
To use Arabica, you'll need an account on a PDS that supports the AT Protocol. The easiest way is to create a Bluesky account at bsky.app.
Once you have an account, simply log in with your handle (e.g., yourname.bsky.social) and start tracking your brews!
Open Source
Arabica is open source software. You can view the code, contribute, or even run your own instance. Visit our GitHub repository to learn more.