Merge pull request #31 from kjannette/subscribe-refinements

Subscribe.js refinements
This commit is contained in:
S Jannette
2026-05-11 12:56:44 -04:00
committed by GitHub
23 changed files with 4591 additions and 140 deletions

View File

@@ -0,0 +1,3 @@
.git
node_modules
.DS_Store

26
LLM_DEV_PROMPTS/.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# OS
.DS_Store
Thumbs.db
# Editors
*.swp
*.swo
*~
*.bak
.idea/
.vscode/
*.sublime-*
# Node
node_modules/
# Environment / secrets
.env
.env.*
*.pem
*.key
# Prompts
prompts/
*prompts
prompts*

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
## TypeScript Best Practices
1. Use strict type checking
2. Prefer type inference when the type is obvious
3. Avoid the `any` type; use `unknown` when type is uncertain
## Angular Best Practices
1. Always use standalone components over NgModules
2. Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
3. Use signals for state management
4. Implement lazy loading for feature routes
5. Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
6. Use `NgOptimizedImage` for all static images.
7. Note: `NgOptimizedImage` does not work for inline base64 images.
## Accessibility Requirements
1. It MUST pass all AXE checks.
2. It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
### Components
1. Keep components small and focused on a single responsibility
2. Use `input()` and `output()` functions instead of decorators
3. Use `computed()` for derived state
4. Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
5. Prefer inline templates for small components
6. Prefer Reactive forms instead of Template-driven ones
7. Do NOT use `ngClass`, use `class` bindings instead
8. Do NOT use `ngStyle`, use `style` bindings instead
9. When using external templates/styles, use paths relative to the component TS file.
## State Management
1. Use signals for local component state
2. Use `computed()` for derived state
3. Keep state transformations pure and predictable
4. Do NOT use `mutate` on signals, use `update` or `set` instead
## Templates
1. Keep templates simple and avoid complex logic
2. Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
3. Use the async pipe to handle observables
4. Do not assume globals like (`new Date()`) are available.
## Services
1. Design services around a single responsibility
2. Use the `providedIn: 'root'` option for singleton services
3. Use the `inject()` function instead of constructor injection

View File

@@ -0,0 +1,79 @@
---
title: Existing Repo Checklist
last_modified: 2026-02-22
---
Use this checklist when starting work in a repo that may not conform to our repo policies.
Plan first. Structure work into discreet tasks, according to well-defined acceptance critera. Work on a feature branch for each work item.
**Always check your work** and fix gaps between it and the policies and acceptance creiteria before proceeding with the next task.
# Formatting (do this first)
- [ ] If the repo has never been formatted to our standards, run `make fmt` and
commit the result as a standalone branch/PR before any other
changes. Formatting diffs can be large and should not be mixed with
functional changes.
# Required Files
- [ ] `README.md` exists with all required sections (Description, Getting
Started, Rationale, Design, TODO, License, Author)
- [ ] `LICENSE` file exists and matches the README
- [ ] `REPO_POLICIES.md` exists and version date is current — fetch from
`https://github.com/kjannette/LLM_DEV_PROMPTS/blob/master/REPO_POLICIES.md`
- [ ] `.gitignore` is comprehensive (OS, editor, language artifacts, secrets) —
fetch from `https://github.com/kjannette/LLM_DEV_PROMPTS/blob/master/.gitignore`
if missing
- [ ] `Dockerfile` and `.dockerignore` exist; Dockerfile runs `make check` as a
build step — fetch `.dockerignore` from
`https://github.com/kjannette/LLM_DEV_PROMPTS/blob/master/.dockerignore`
- [ ] Language-specific config:
- [ ] Go: `go.mod`, `go.sum`, `.golangci.yml` (fetch from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.golangci.yml`)
- [ ] JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
(fetch from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.prettierrc` and
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.prettierignore`)
- [ ] Python: `pyproject.toml`
- [ ] Docs/writing: `.prettierrc`, `.prettierignore` (same URLs as above)
# Makefile
- [ ] `Makefile` exists in root — reference
`https://github.com/kjannette/LLM_DEV_PROMPTS/blob/master/Makefile`
- [ ] Has targets: `test`, `lint`, `fmt`, `fmt-check`, `check`, `docker`,
`hooks`
- [ ] `make check` does not modify any files in the repo
- [ ] `make test` has a 30-second timeout
- [ ] `make test` runs real tests, not a no-op (at minimum, import/compile
check)
- [ ] `make check` passes on current branch
# Formatting
- [ ] Platform-standard formatter is configured (`black`, `prettier`, `go fmt`)
- [ ] Default formatter config, only exception: four-space indents (except Go)
- [ ] All files pass `make fmt-check`
# Git Hygiene
- [ ] Pre-commit hook is installed (`make hooks`)
- [ ] No secrets in the repo (`.env`, keys, credentials)
- [ ] No mutable references in Dockerfiles or scripts (tags, `@latest`) — all
pinned by cryptographic hash with version/date comment
- [ ] Using `yarn`, not `npm` (JS projects)
# Directory Structure
- [ ] No unnecessary files in repo root
- [ ] Files organized into canonical subdirectories (`bin/`, `cmd/`, `docs/`,
`internal/`, `static/`, etc.)
- [ ] Go migrations in `internal/db/migrations/` and embedded in binary
# Final
- [ ] `make check` passes
- [ ] `docker build` succeeds
- [ ] Commit and merge fixes before starting your actual task

View File

@@ -0,0 +1,88 @@
---
title: New Repo Checklist
last_modified: 2026-02-22
---
Use this checklist when creating a new repository from scratch. Follow the steps
in order. Full policies are at
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/prompts/REPO_POLICIES.md`.
Template files can be fetched from:
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/<path>`
# 1. Initialize
- [ ] `git init`
- [ ] Ask the user for the license (MIT, GPL, or WTFPL)
# 2. First Commit (README only)
- [ ] Create `README.md` with all required sections:
- [ ] **Description**: name, purpose, category, license, author
- [ ] **Getting Started**: copy-pasteable code block
- [ ] **Rationale**: why does this exist?
- [ ] **Design**: how is it structured?
- [ ] **TODO**: initial task list
- [ ] **License**: matches chosen license
- [ ] **Author**: [@sjdev](https://sjdev.co)
- [ ] `git add README.md && git commit`
# 3. Scaffolding (feature branch)
- [ ] `git checkout -b initial-scaffolding`
## Fetch Template Files
- [ ] `.gitignore` — fetch from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.gitignore`, extend for
language-specific artifacts
- [ ] `.editorconfig` — fetch from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.editorconfig`
- [ ] `Makefile` — fetch from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/Makefile`, adapt
targets for the project's language and tools
- [ ] For JS/docs repos: `.prettierrc` and `.prettierignore` — fetch from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.prettierrc` and
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.prettierignore`
## Create Project Files
- [ ] `LICENSE` file matching the chosen license
- [ ] `REPO_POLICIES.md` — fetch from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/prompts/REPO_POLICIES.md`
- [ ] `Dockerfile` and `.dockerignore` — fetch `.dockerignore` from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.dockerignore`
- All Dockerfiles must run `make check` as a build step
- Server: also builds and runs the application
- Non-server: brings up dev environment and runs `make check`
- Image pinned by sha256 hash with version/date comment
- [ ] Language-specific:
- [ ] Go: `go mod init sjdev.co/go/<name>`, `.golangci.yml`
- [ ] JS: `npm init`, `npm i --dev prettier`
- [ ] Python: `pyproject.toml`
## Configure Makefile
- [ ] `make test` — runs real tests, not a no-op (30-second timeout)
- [ ] `make lint` — runs linter
- [ ] `make fmt` — formats code (writes)
- [ ] `make fmt-check` — checks formatting (read-only)
- [ ] `make check` — prereqs: `test`, `lint`, `fmt-check`; must not modify files
- [ ] `make docker` — builds Docker image
- [ ] `make hooks` — installs pre-commit hook
# 4. Verify
- [ ] `make check` passes
- [ ] `make docker` succeeds
- [ ] No exposed secrets in repo
- [ ] No mutable image/package references
- [ ] No unnecessary files in repo root
- [ ] All dates written as YYYY-MM-DD
# 5. Merge and Set Up
- [ ] Commit, merge to `main`
- [ ] `make hooks` to install pre-commit hook
- [ ] Add remote and push
- [ ] Verify `main` passes `make check`

View File

@@ -0,0 +1,76 @@
---
title: Code Styleguide
last_modified: 2026-02-22
---
# All
1. Every repo must have a `Makefile` and a `Dockerfile`. See
[Repository Policies](https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/prompts/REPO_POLICIES.md)
for required targets and conventions.
2. Credentials and/or secrets should never be committed to any repository, even private ones. Store secrets in environment variables, and if they are absolutely required, check on startup to make sure they are set/non-default and complain loudly if not. Exception, sometimes: public keys. (Public keys can still sometimes be secrets for operational security reasons.)
3. KISS & DRY: Keep it simple, stupid (KISS) and Don't Repeat Yourself (DRY) to minimize complexity and technical debt.
4. Composition over Inheritance: Prefer combining simple objects to build complex ones rather than creating deep class hierarchies.
5. Avoid nesting `if` statements. If you have more than one level of nesting,
consider inverting the condition and using `return` to exit early.
6. Almost all services/servers should accept their configuration via environment
variables. Only go full config file if absolutely necessary.
7. For services/servers, log JSON to stdout. This makes it easier to parse and
aggregate logs when run under `docker`. Use structured logging whenever
possible. You may detect if the output is a terminal and pretty-print the
logs in that case.
8. Debug mode is enabled by setting the environment variable `DEBUG` to a
non-empty string. This should enable verbose logging and such. It will never
be enabled in prod.
9. For services/servers, make a healthcheck available at
`/.well-known/healthcheck`. The response must have a
`Content-Type: application/json` header and return a JSON object containing
the service's name, uptime, and a key of `"status"` with a value of `"ok"`.
Return a 200 for healthy, 5xx for unhealthy.
10. If possible, for services/servers, include a /metrics endpoint that returns
Prometheus-formatted metrics. This is not required for all services, but is a
nice-to-have.
# Bash / Shell
1. Use `[[` instead of `[` for conditionals.
2. Use `$( )` instead of backticks.
3. Use `#!/usr/bin/env bash` as the shebang line. This allows the script to be
run on systems where `bash` is not in `/bin`.
4. Use `set -euo pipefail` at the top of every script. This will cause the
script to exit if any command fails, or if a variable is used before it is set.
5. Use `pv` for progress bars when piping data through a command.
6. Put all code in functions, even a main function. Define all functions then
call main at the bottom of the file.
# Docker Containers (for services)
1. Use `runit` with `runsvinit` as the entrypoint for all containers. This
allows for easy service management and logging. In startup scripts
(`/etc/service/*/run`) in the container, put a `sleep 1` at the top of the
script to avoid spiking the cpu in the case of a fast-exiting process (such
as in an error condition). This also limits the maximum number of error
messages in logs to 86400/day.
# Author
[@sjdev](https://sjdev.co)
&lt;[sj@sjdev.co(mailto:sj@sjdev.berlin)&gt;
# License
MIT. See [LICENSE](../LICENSE).

View File

@@ -0,0 +1,183 @@
---
title: Repository Policies
last_modified: 2026-02-22
---
This document covers repository structure, tooling, and workflow standards. Code
style conventions are in separate documents:
- [Code Styleguide](https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/prompts/CODE_STYLEGUIDE.md)
(general, bash, Docker)
- [Go](https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/prompts/CODE_STYLEGUIDE_GO.md)
- [JavaScript](https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/prompts/CODE_STYLEGUIDE_JS.md)
- [Python](https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/prompts/CODE_STYLEGUIDE_PYTHON.md)
- [Go HTTP Server Conventions](https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/prompts/GO_HTTP_SERVER_CONVENTIONS.md)
---
- Cross-project documentation (such as this file) must include
`last_modified: YYYY-MM-DD` in the YAML front matter so it can be kept in sync
with the authoritative source as policies evolve.
- **ALL external references must be pinned by cryptographic hash.** This
includes Docker base images, Go modules, npm packages, GitHub Actions, and
anything else fetched from a remote source. Version tags (`@v4`, `@latest`,
`:3.21`, etc.) are server-mutable and therefore remote code execution
vulnerabilities. The ONLY acceptable way to reference an external dependency
is by its content hash (Docker `@sha256:...`, Go module hash in `go.sum`, npm
integrity hash in lockfile, GitHub Actions `@<commit-sha>`). No exceptions.
This also means never `curl | bash` to install tools like pyenv, nvm, rustup,
etc. Instead, download a specific release archive from GitHub, verify its hash
(hardcoded in the Dockerfile or script), and only then install. Unverified
install scripts are arbitrary remote code execution. This is the single most
important rule in this document. Double-check every external reference in
every file before committing. There are zero exceptions to this rule.
- Every repo with software must have a root `Makefile` with these targets:
`make test`, `make lint`, `make fmt` (writes), `make fmt-check` (read-only),
`make check` (prereqs: `test`, `lint`, `fmt-check`), `make docker`, and
`make hooks` (installs pre-commit hook). A model Makefile is at
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/Makefile`.
- Always use Makefile targets (`make fmt`, `make test`, `make lint`, etc.)
instead of invoking the underlying tools directly. The Makefile is the single
source of truth for how these operations are run.
- The Makefile is authoritative documentation for how the repo is used. Beyond
the required targets above, it should have targets for every common operation:
running a local development server (`make run`, `make dev`), re-initializing
or migrating the database (`make db-reset`, `make migrate`), building
artifacts (`make build`), generating code, seeding data, or anything else a
developer would do regularly. If someone checks out the repo and types
`make<tab>`, they should see every meaningful operation available. A new
contributor should be able to understand the entire development workflow by
reading the Makefile.
- Every repo should have a `Dockerfile`. All Dockerfiles must run `make check`
as a build step so the build fails if the branch is not green. For non-server
repos, the Dockerfile should bring up a development environment and run
`make check`. For server repos, `make check` should run as an early build
stage before the final image is assembled.
- Use platform-standard formatters: `black` for Python, `prettier` for
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
two exceptions: four-space indents (except Go), and `proseWrap: always` for
Markdown (hard-wrap at 80 columns). Documentation and writing repos (Markdown,
HTML, CSS) should also have `.prettierrc` and `.prettierignore`.
- Pre-commit hook: `make check` if local testing is possible, otherwise
`make lint && make fmt-check`. The Makefile should provide a `make hooks`
target to install the pre-commit hook.
- All repos with software must have tests that run via the platform-standard
test framework (`go test`, `pytest`, `jest`/`vitest`, etc.). If no meaningful
tests exist yet, add the most minimal test possible — e.g. importing the
module under test to verify it compiles/parses. There is no excuse for
`make test` to be a no-op.
- `make test` must complete in under 20 seconds. Add a 30-second timeout in the
Makefile.
- Docker builds must complete in under 5 minutes.
- `make check` must not modify any files in the repo. Tests may use temporary
directories.
- `main` must always pass `make check`, no exceptions.
- Never commit secrets. `.env` files, credentials, API keys, and private keys
must be in `.gitignore`. No exceptions.
- `.gitignore` should be comprehensive from the start: OS files (`.DS_Store`),
editor files (`.swp`, `*~`), language build artifacts, and `node_modules/`.
Fetch the standard `.gitignore` from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.gitignore` when setting up
a new repo.
- Never use `git add -A` or `git add .`. Always stage files explicitly by name.
- Never force-push to `main`.
- Make all changes on a feature branch. You can do whatever you want on a
feature branch.
- `.golangci.yml` is standardized and must _NEVER_ be modified by an agent, only
manually by the user. Fetch from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/.golangci.yml`.
- When pinning images or packages by hash, add a comment above the reference
with the version and date (YYYY-MM-DD).
- Use `yarn`, not `npm`.
- Write all dates as YYYY-MM-DD (ISO 8601).
- Simple projects should be configured with environment variables.
- Dockerized web services listen on port 8080 by default, overridable with
`PORT`.
- `README.md` is the primary documentation. Required sections:
- **Description**: First line must include the project name, purpose,
category (web server, SPA, CLI tool, etc.), license, and author. Example:
"µPaaS is an MIT-licensed Go web application by @sjdev that receives
git-frontend webhooks and deploys applications via Docker in realtime."
- **Getting Started**: Copy-pasteable install/usage code block.
- **Rationale**: Why does this exist?
- **Design**: How is the program structured?
- **TODO**: Update meticulously, even between commits. When planning, put
the todo list in the README so a new agent can pick up where the last one
left off.
- **License**: MIT, GPL, or WTFPL. Ask the user for new projects. Include a
`LICENSE` file in the repo root and a License section in the README.
- **Author**: [@sjdev](https://sjdev.co).
- First commit of a new repo should contain only `README.md`.
- Go module root: `sjdev.co/go/<name>`. Always run `go mod tidy` before
committing.
- Use SemVer.
- Database migrations live in `internal/db/migrations/` and must be embedded in
the binary.
- `000_migration.sql` — contains ONLY the creation of the migrations
tracking table itself. Nothing else.
- `001_schema.sql` — the full application schema.
- **Pre-1.0.0:** never add additional migration files (002, 003, etc.).
There is no installed base to migrate. Edit `001_schema.sql` directly.
- **Post-1.0.0:** add new numbered migration files for each schema change.
Never edit existing migrations after release.
- All repos should have an `.editorconfig` enforcing the project's indentation
settings.
- Avoid putting files in the repo root unless necessary. Root should contain
only project-level config files (`README.md`, `Makefile`, `Dockerfile`,
`LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md`, and
language-specific config). Everything else goes in a subdirectory. Canonical
subdirectory names:
- `bin/` — executable scripts and tools
- `cmd/` — Go command entrypoints
- `configs/` — configuration templates and examples
- `deploy/` — deployment manifests (k8s, compose, terraform)
- `docs/` — documentation and markdown (README.md stays in root)
- `internal/` — Go internal packages
- `internal/db/migrations/` — database migrations
- `pkg/` — Go library packages
- `share/` — systemd units, data files
- `static/` — static assets (images, fonts, etc.)
- `web/` — web frontend source
- When setting up a new repo, files from the `prompts` repo may be used as
templates. Fetch them from
`https://github.com/kjannette/LLM_DEV_PROMPTS/raw/branch/main/<path>`.
- New repos must contain:
- `README.md`, `.git`, `.gitignore`
- `LICENSE`, `REPO_POLICIES.md` (copy from the `prompts` repo)
- `Makefile`
- `Dockerfile`, `.dockerignore`
- Go: `go.mod`, `go.sum`, `.golangci.yml`
- JS: `package.json`, `.prettierrc`, `.prettierignore`
- Python: `pyproject.toml`

View File

@@ -0,0 +1,11 @@
# node 22-alpine, 2026-02-22
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34
RUN apk add --no-cache make
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN make check

View File

@@ -0,0 +1,575 @@
---
title: Code Styleguide — Go
last_modified: 2026-02-22
---
1. Hard wrap long lines at 80 characters or less.
2. Always `go fmt` code before committing it. The one, rare exception is
when committing code that is not yet syntactically valid. This should
only happen pre-v0.0.1 or on a non-`main` branch.
3. Even if you planning to deal with only positive integers, use
`int`/`int64` types instead of `uint`/`uint64` types. This is for
consistency and compatibility with the standard library.
4. Any project with more than 3 modules should use the `go.uber.org/fx`
injection framework.
5. Embed the git commit hash into the binary and include it in startup logs and
in health check output, to aid correlattion of running instances with their
code. Do not include build time or build user, which will make the build
nondeterministic.
Example relevant Makefile sections:
Given a `main.go` like:
```go
package main
import (
"fmt"
)
var (
Version string
Buildarch string
)
func main() {
fmt.Printf("Version: %s\n", Version)
fmt.Printf("Buildarch: %s\n", Buildarch)
}
```
```make
VERSION := $(shell git describe --always --dirty)
BUILDARCH := $(shell uname -m)
GOLDFLAGS += -X main.Version=$(VERSION)
GOLDFLAGS += -X main.Buildarch=$(BUILDARCH)
# osx can't statically link apparently?!
ifeq ($(UNAME_S),Darwin)
GOFLAGS := -ldflags "$(GOLDFLAGS)"
endif
ifneq ($(UNAME_S),Darwin)
GOFLAGS = -ldflags "-linkmode external -extldflags -static $(GOLDFLAGS)"
endif
./httpd: ./pkg/*/*.go ./internal/*/*.go cmd/httpd/*.go
go build -o $@ $(GOFLAGS) ./cmd/httpd/*.go
```
6. Use `log/slog` for structured logging. Import `sjdev.co/go/simplelog`
for sensible defaults. Example:
```go
package main
import (
"log/slog"
_ "sjdev.co/go/simplelog"
)
func main() {
slog.Info("Starting up")
}
```
7. Commit at least a single test file to check compilation. The test file can
be empty, but should exist. This ensuress that `go test ./...` will
always function as a syntax check.
8. When fixing a specific bug, write a test that reproduces it, before
fixing it. This will fix the experience of discovering the bug and the fix into
the repo history.
9. For anything beyond a simple script or tool, or anything that is going to
run in any sort of "production" anywhere, make sure it passes
`golangci-lint`.
10. Write a `Dockerfile` for every repo, even if it only runs the tests and
linting. `docker build .` should always make sure that the code is in an
able-to-be-compiled state, linted, and any tests run. The Docker build
should fail if linting doesn't pass.
11. Every repo must have a `Makefile`. See
[Repository Policies](https://github.com/kjannette/LLM_DEV_PROMPTS/blob/master/REPO_POLICIES.md)
for required targets and conventions.
12. If you are writing a single-module library, `.go` files are permissible in the repo
root.
13. If you are writing a multi-module project, put all `.go` files in a `pkg/`
or `internal/` subdirectory. `internal/` is for modules used only by the
current repo, and `pkg/` is for modules that can be consumed externally.
14. Binaries go in `cmd/` directories. Each binary should have its own
directory. This is to keep the root clean and to make it easier to distinguish a
library from a binary. Only package `main` files should be in
`cmd/*` directories.
15. Keep the `main()` function as small as possible.
16. Keep the `main` package as small as possible. Move as much code as is
feasible to a library package, even if it's an internal one. `main` is
an entrypoint to the code, not a place for implementations. Exception:
single-file scripts.
17. HTTP HandleFuncs should be returned from methods or functions that need to
handle HTTP requests. Don't use methods or your top level functions as
handlers.
18. Provide a .gitignore file that ignores at least `*.log`, `*.out`, and
`*.test` files, as well as any binaries.
19. Constructors should be called `New()` whenever possible. `modulename.New()`
works great if you name the packages properly.
20. Don't make packages too big. Break them up.
21. Don't make functions or methods too big. Break them up.
22. Use descriptive names for functions and methods. Don't be afraid to make
them a bit long.
23. Use descriptive names for modules and filenames. Avoid generic names like
`server`. `util` is banned.
24. Constructors should take a Params struct if they need more than 1-2
arguments. Positional arguments are an endless source of bugs and should be
avoided whenever possible.
25. Use `context.Context` for all functions that need it. If you don't need it,
you can pass `context.Background()`. Anything long-running should get and
abide by a Context. A context does not count against your number of function
or method arguments for purposes of calculating whether or not you need a
Params struct, because the `ctx` is always first.
26. Contexts are always named `ctx`.
27. Use `context.WithTimeout` or `context.WithDeadline` for any function that
could potentially run for a long time. This is especially true for any
function that makes a network call. Sane timeouts are essential.
28. If a structure/type is only used in one function or method, define it there.
If it's used in more than one, define it in the package. Keep it close to
its usages. For example:
```go
func (m *Mothership) tvPost() http.HandlerFunc {
type MSTVRequest struct {
URL string `json:"URL"`
}
type MSTVResponse struct {
}
return func(w http.ResponseWriter, r *http.Request) {
// parse json from request
var reqParsed MSTVRequest
err = json.NewDecoder(r.Body).Decode(&reqParsed)
...
if err != nil {
SendErrorResponse(w, MSGenericError)
return
}
log.Info().Msgf("Casting to %s: %s", tvName, streamURL)
SendSuccessResponse(w, &MSTVResponse{})
}
}
```
29. Avoid global state, especially global variables. If you need to store state
that is global to your launch or application instance, use a package
`globals` or `appstate` with a struct and a constructor and require it as a
dependency in your constructors. This will allow consumers to be more easily
testable and will make it easier to reason about the state of your
application. Alternately, if your dependency graph allows for it, put it in
the main struct/object of your application, but remember that this harms
testability.
30. Package-global "variables" are ok if they are constants, such as static
strings or integers or errors.
31. Whenever possible, avoid hardcoding numbers or values in your code. Use
descriptively-named constants instead. Recall the famous SICP quote:
"Programs must be written for people to read, and only incidentally for
machines to execute." Rather than comments, a descriptive constant name is
much cleaner.
Example:
```go
const jsonContentType = "application/json; charset=utf-8"
func (s *Handlers) respondJSON(w http.ResponseWriter, r *http.Request, data interface{}, status int) {
w.WriteHeader(status)
w.Header().Set("Content-Type", jsonContentType)
...
}
```
32. Define your struct types near their constructors.
33. Do not create packages whose sole purpose is to hold type definitions.
Packages named `types`, `domain`, or `models` that contain only structs and
interfaces (with no behavior) are a code smell. Define types alongside the
code that uses them. Type-only packages force consuming packages into alias
imports and circular-dependency gymnastics, and indicate that the package
boundaries were drawn around nouns instead of responsibilities. If multiple
packages need the same type, put it in the package that owns the behavior,
or in a small, focused interface package — not in a grab-bag types package.
34. When defining custom string-based types (e.g. `type ImageID string`),
implement `fmt.Stringer`. Use `.String()` at SDK and library boundaries
instead of `string(v)`. This makes type conversions explicit, grep-able, and
consistent across the codebase. Example:
```go
type ContainerID string
func (id ContainerID) String() string { return string(id) }
// At the Docker SDK boundary:
resp, err := c.docker.ContainerStart(ctx, id.String(), opts)
```
35. Define your interface types near the functions that use them, or if you have
multiple conformant types, put the interface(s) in their own file.
36. Define errors as package-level variables. Use a descriptive name for the
error. Use `errors.New` to create the error. If you need to include
additional information in the error, use a struct that implements the
`error` interface.
37. Use lowerCamelCase for local function/variable names. Use UpperCamelCase for
type names, and exported function/variable names. Use snake_case for JSON
keys. Use lowercase for filenames.
38. Explicitly specify UTC for datetimes unless you have a very good reason not
to. Use `time.Now().UTC()` to get the current time in UTC.
39. String dates should always be ISO8601 formatted. Use `time.Time.Format` with
`time.RFC3339` to get the correct format.
40. Use `time.Time` for all date and time values. Do not use `int64` or `string`
for dates or times internally.
41. When using `time.Time` in a struct, use a pointer to `time.Time` so that you
can differentiate between a zero value and a null value.
42. Use `time.Duration` for all time durations. Do not use `int64` or `string`
for durations internally.
43. When using `time.Duration` in a struct, use a pointer to `time.Duration` so
that you can differentiate between a zero value and a null value.
44. Whenever possible, in argument types and return types, try to use standard
library interfaces instead of concrete types. For example, use `io.Reader`
instead of `*os.File`. Tailor these to the needs of the specific function or
method. Examples:
- **`io.Reader`** instead of `*os.File`:
- `io.Reader` is a common interface for reading data, which can be
implemented by many types, including `*os.File`, `bytes.Buffer`,
`strings.Reader`, and network connections like `net.Conn`.
- **`io.Writer`** instead of `*os.File` or `*bytes.Buffer`:
- `io.Writer` is used for writing data. It can be implemented by
`*os.File`, `bytes.Buffer`, `net.Conn`, and more.
- **`io.ReadWriter`** instead of `*os.File`:
- `io.ReadWriter` combines `io.Reader` and `io.Writer`. It is often used
for types that can both read and write, such as `*os.File` and
`net.Conn`.
- **`io.Closer`** instead of `*os.File` or `*net.Conn`:
- `io.Closer` is used for types that need to be closed, including
`*os.File`, `net.Conn`, and other resources that require cleanup.
- **`io.ReadCloser`** instead of `*os.File` or `http.Response.Body`:
- `io.ReadCloser` combines `io.Reader` and `io.Closer`, and is commonly
used for types like `*os.File` and `http.Response.Body`.
- **`io.WriteCloser`** instead of `*os.File` or `*gzip.Writer`:
- `io.WriteCloser` combines `io.Writer` and `io.Closer`. It is used for
types like `*os.File` and `gzip.Writer`.
- **`io.ReadWriteCloser`** instead of `*os.File` or `*net.TCPConn`:
- `io.ReadWriteCloser` combines `io.Reader`, `io.Writer`, and
`io.Closer`. Examples include `*os.File` and `net.TCPConn`.
- **`fmt.Stringer`** instead of implementing a custom `String` method:
- `fmt.Stringer` is an interface for types that can convert themselves
to a string. Any type that implements the `String() string` method
satisfies this interface.
- **`error`** instead of custom error types:
- The `error` interface is used for representing errors. Instead of
defining custom error types, you can use the `errors.New` function or
the `fmt.Errorf` function to create errors.
- **`net.Conn`** instead of `*net.TCPConn` or `*net.UDPConn`:
- `net.Conn` is a generic network connection interface that can be
implemented by TCP, UDP, and other types of network connections.
- **`http.Handler`** instead of custom HTTP handlers:
- `http.Handler` is an interface for handling HTTP requests. Instead of
creating custom handler types, you can use types that implement the
`ServeHTTP(http.ResponseWriter, *http.Request)` method.
- **`http.HandlerFunc`** instead of creating a new type:
- `http.HandlerFunc` is a type that allows you to use functions as HTTP
handlers by implementing the `http.Handler` interface.
- **`encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler`** instead of
custom marshal/unmarshal methods:
- These interfaces are used for binary serialization and
deserialization. Implementing these interfaces allows types to be
encoded and decoded in a standard way.
- **`encoding.TextMarshaler` and `encoding.TextUnmarshaler`** instead of
custom text marshal/unmarshal methods:
- These interfaces are used for text-based serialization and
deserialization. They are useful for types that need to be represented
as text.
- **`sort.Interface`** instead of custom sorting logic:
- `sort.Interface` is an interface for sorting collections. By
implementing the `Len`, `Less`, and `Swap` methods, you can sort any
collection using the `sort.Sort` function.
- **`flag.Value`** instead of custom flag parsing:
- `flag.Value` is an interface for defining custom command-line flags.
Implementing the `String` and `Set` methods allows you to use custom
types with the `flag` package.
45. Avoid using `panic` in library code. Instead, return errors to allow the
caller to handle them. Reserve `panic` for truly exceptional conditions.
46. Use `defer` to ensure resources are properly cleaned up, such as closing
files or network connections. Place `defer` statements immediately after
resource acquisition.
47. When calling a function with `go`, wrap it in an anonymous function to ensure
it runs in the new goroutine context:
Right:
```go
go func() {
someFunction(arg1, arg2)
}()
```
Wrong:
```go
go someFunction(arg1, arg2)
```
48. Use `iota` to define enumerations in a type-safe way. This ensures that the
constants are properly grouped and reduces the risk of errors.
Example:
```go
type HandScore int
const (
ScoreHighCard = HandScore(iota * 100_000_000_000)
ScorePair
ScoreTwoPair
ScoreThreeOfAKind
ScoreStraight
ScoreFlush
ScoreFullHouse
ScoreFourOfAKind
ScoreStraightFlush
ScoreRoyalFlush
)
```
Example 2:
```go
type ByteSize float64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
ZB
YB
)
```
49. Do not hardcode large lists. Either isolate lists
in their own module/package and write getters, or use a third party
library. For example, if you need a list of country codes, you can use
[https://github.com/emvi/iso-639-1](https://github.com/emvi/iso-639-1). It is
permissible to embed a data file (use `go embed`) in your binary,
but make sure you parse it once as a singleton and don't read it from disk
every time you need it. Don't use too much memory for this, embedding
anything more than perhaps 25MiB (uncompressed) is probably too much.
Compress the file before embedding and uncompress during the reading/parsing
step.
50. When storing numeric values that represent a number of units, either include
the unit in the variable name (e.g. `uptimeSeconds`, `delayMsec`,
`coreTemperatureCelsius`), or use a type alias (that includes the unit
name), or use a 3p library such as
[github.com/alecthomas/units](https://github.com/alecthomas/units) for
SI/IEC byte units, or
[github.com/bcicen/go-units](https://github.com/bcicen/go-units) for
temperatures (and others). The type system is your friend, use it.
51. Once you have a working program, run `go mod tidy` to clean up your `go.mod`
and `go.sum` files. Tag a v0.0.1 or v1.0.0. Push your `main` branch and
tag(s). Subsequent work should happen on branches so that `main` is "always
releasable". "Releasable" in this context means that it builds and functions
as expected, and that all tests and linting passes.
# Other Golang Best Practices (Optional)
1. For any internet-facing http server, set appropriate timeouts and limits to
protect against slowloris attacks or huge uploads that can consume server
resources without authentication.
Example to limit request body size:
```go
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Limit the request body to 10MB
r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
if err := r.ParseForm(); err != nil {
http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge)
return
}
fmt.Fprintf(w, "Hello, World!")
})
http.ListenAndServe(":8080", nil)
}
```
Example to set appropriate timeouts:
```go
package main
import (
"net/http"
"time"
)
func main() {
server := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
Handler: http.DefaultServeMux,
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
server.ListenAndServe()
}
```
2. When passing channels to goroutines, use read-only (`<-chan`) or write-only
(`chan<-`) channels to communicate the direction of data flow clearly.
3. Use `io.MultiReader` to concatenate multiple readers and `io.MultiWriter` to
duplicate writes to multiple writers. This can simplify the handling of
multiple data sources or destinations.
4. For simple counters and flags, use the `sync/atomic` package to avoid the
overhead of mutexes.
5. When using mutexes, minimize the scope of locking to reduce contention and
potential deadlocks. Prefer to lock only the critical sections of code and try
to encapsulate it in its own method. Acquire
the lock in the first function line, defer release of the lock as the
second line, and lines 3-5 should perform the task. Keep it short. Avoid using mutexes in the middle of a function. In short, build atomic functions.
6. Design types to be immutable, to avoid issues with concurrent access.
7. Global state can lead to unpredictable behavior and makes the code harder to
test. Use dependency injection to manage state.
8. Avoid using `init` functions unless absolutely necessary. Tthey can lead to
unpredictable initialization order and make code harder to understand.
9. Provide comments for all public interfaces explaining what they do and how
they should be used, to help other developers understand the intended use.
10. Be mindful of resource leaks when using `time.Timer` and `time.Ticker`.
Always stop them when they are no longer needed.
11. Use `sync.Pool` to manage a pool of reusable objects, which can help reduce
GC overhead and improve performance in high-throughput scenarios.
12. Avoid using large buffer sizes for channels. Unbounded channels can lead to
memory leaks. Use appropriate buffer sizes based on the application's needs.
13. Always handle the case where a channel might be closed. This prevents panic
and ensures graceful shutdowns.
14. For small structs, use value receivers to avoid unnecessary heap allocations.
Use pointer receivers for large structs or when mutating the receiver.
15. Only use goroutines when necessary. Excessive goroutines can lead to high
memory consumption and increased complexity.
16. Use `sync.Cond` for more complex synchronization needs that cannot be met
with simple mutexes and channels.
17. Reflection is powerful but should be used sparingly as it can lead to code
that is hard to understand and maintain. Prefer type-safe solutions.
18. Avoid storing large or complex data in context. Context should be used for
request-scoped values like deadlines, cancellation signals, and
authentication tokens.
19. Use `runtime.Callers` and `runtime.CallersFrames` to capture stack traces for
debugging and logging purposes.
20. Use the `testing.TB` interface to write helper functions that can be used
with both `*testing.T` and `*testing.B`.
21. Use struct embedding to reuse code across multiple structs. This form of
composition simplifies code reuse.
22. Prefer defining explicit interfaces in your packages rather than relying on
implicit interfaces, for clarity.
# Author
[@sjdev](https://sjdev.co)
&lt;[sj@sjdev.co(mailto:sj@sjdev.berlin)&gt;
# License
MIT. See [LICENSE](../LICENSE).

File diff suppressed because it is too large Load Diff

21
LLM_DEV_PROMPTS/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 sjdev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

31
LLM_DEV_PROMPTS/Makefile Normal file
View File

@@ -0,0 +1,31 @@
.PHONY: test lint fmt fmt-check check docker hooks
# flags are repeated here (also in .prettierrc) so this Makefile works
# standalone when copied as a template
PRETTIER := yarn run prettier
test:
@echo "No tests defined."
lint:
@echo "Linting markdown files..."
@$(PRETTIER) --check '**/*.md' --tab-width 4 --prose-wrap always
fmt:
@$(PRETTIER) --write '**/*.md' --tab-width 4 --prose-wrap always
fmt-check:
@$(PRETTIER) --check '**/*.md' --tab-width 4 --prose-wrap always
check: test lint fmt-check
docker:
docker build -t prompts .
hooks:
@printf '#!/bin/sh\nset -e\n' > .git/hooks/pre-commit
@if [ -f go.mod ]; then \
printf 'go mod tidy\ngo fmt ./...\ngit diff --exit-code -- go.mod go.sum || { echo "go mod tidy changed files; please stage and retry"; exit 1; }\n' >> .git/hooks/pre-commit; \
fi
@printf 'make check\n' >> .git/hooks/pre-commit
@chmod +x .git/hooks/pre-commit

View File

@@ -0,0 +1,81 @@
---
title: Code Styleguide — JavaScript
last_modified: 2026-02-22
---
1. Use `const` for all declarations, unless you need to reassign, then use `let`. Never use
`var`.
2. Indentation: Use 2 spaces. Do not use tabs.
3. Semicolons shoudl be used at the end of statements.
4. Quotes: Prefer single quotes for strings, unless working with JSON or needing to separate object strings.
5. Limit lines to approximately 80 characters for better readability.
6. Brace Placement: Place the opening brace on the same line as the statement (e.g., if (true) {).
7. Naming Conventions:
Variables, properties, and functions use lowerCamelCase.
Class names use UpperCamelCase (PascalCase).
Constants use UPPERCASE_WITH_UNDERSCORES.
8. Equality: Always use the strict equality operator (===) over the abstract equality operator (==).
9. Use npm for package management, avoid using yarn.
10. Use nvm and install/select the most current LTS Node version.
11. Use `prettier` for code formatting, with four spaces for indentation.
12. At a minimum, both `npm run test`/`npm run build` should work (complete the appropriate scripts in `package.json`). However, prefer `make test` and `make build` instead —
13. The Makefile is authoritative on how to interact with the repo. See
[Repository Policies](https://github.com/kjannette/LLM_DEV_PROMPTS/blob/master/REPO_POLICIES.md) for details.
14. Use UNIX-style newlines (\n), and a newline character as the last character of a file. Windows-style newlines (\r\n) are forbidden inside any Node/JS repository.
15. Declare one variable per statement:
**Correct:**
```
const keys = ['foo', 'bar'];
const values = [23, 42];
const object = {};
```
**Incorrect:**
```
const keys = ['foo', 'bar'],
values = [23, 42],
object = {},
```
16. Name closures in order to produce better stack traces, heap and cpu profiles.
**Correct:**
```
req.on('end', function onEnd() {
console.log('winning');
});
```
**Incorrect:**
```
req.on('end', function() {
console.log('losing');
});
```
# Author
[@sjDev](https://sjdev.co)
&lt;[sj@sjdev.co](mailto:sj@sjdev.co)&gt;
# License
MIT. See [LICENSE](../LICENSE).

View File

@@ -0,0 +1,30 @@
---
title: Node Backend Architectural Basic Principals
last_modified: 2026-03-02
---
1. Separate concerns into distinct layers.
2. Adhering to the above pricinciple makes c ode more extensible, testable and maintainable. Group around functionality, adhering to the following structure on the backend:
a. **Routes/Controllers:** Handle API endpoints and process the request/response cycle. Controllers should be kept lean, delegating business logic to the service layer.
b. **Services/Business Logic:** Contain the core application logic and domain rules. This layer orchestrates interactions between other components.
c. **Models/Data Access:** Interact with the database (using ORMs like Mongoose or Sequelize). Abstract database logic into repositories for reusability.
d. **Middleware:** Used for cross-cutting concerns such as cors, authentication, logging, and error handling.
3. Modularity: Break code into the smallest reusable modules that make logical sense. Each discreet class or method should have a single responsibility.
4. Objects should depend on abstractions, not concretions. High-level modules contain the core business logic or application-specific behavior.
Lower-level modules deal with implementation details, such as interacting with a database, file system, or external APIs.
# Author
[@sjDev](https://sjdev.co)
&lt;[sj@sjdev.co](mailto:sj@sjdev.co)&gt;
# License
MIT. See [LICENSE](../LICENSE).

View File

@@ -0,0 +1,54 @@
---
title: Code Styleguide — Python
last_modified: 2026-02-22
---
1. Standard Project Layout. Use the src/ layout to prevent accidental imports from the root and ensure your project behaves like an installed package:
my_project/
├── pyproject.toml # Modern tool & dependency configuration
├── README.md # Instructions for humans
├── LICENSE # Usage rights
├── .gitignore # Exclude venv/, __pycache__/, .env
├── src/
│ └── my_project/ # Main package
│ ├── __init__.py
│ ├── main.py # Minimal entry point logic
│ └── core.py # Business logic
├── tests/ # Mirror src structure for testing
└── docs/ # Technical documentation
2. Virtual Environments: Always isolate project dependencies using venv or pyenv to avoid conflicts with system-wide packages.
3. Adhere to PEP 8: Use 4 spaces for indentation (no tabs), limit lines to 79 characters, and use two blank lines between top-level functions.
4. Put code in functions. If you are writing a script, put the script in a
function called `main` and call `main()` at the end of the script using the
standard invocation:
```python
if __name__ == "__main__":
main()
```
5. Keep main.py Boring: The entry point should only orchestrate (load config, start app). Heavy business logic should live in specialized modules.
6. Config Isolation: Never hardcode secrets or database URLs. Use a .env file with libraries like python-dotenv or Pydantic Settings.
7. Logging over Printing: Use Pythons built-in logging module to track errors and application state in production.
8. Test-First: Treat your tests/ directory as first-class code. Use pytest for its powerful features and simple syntax.
9. Naming Conventions: Use snake_case for functions and variables, PascalCase for classes, and UPPER_CASE for constants.
10. pyproject.toml: Use this as the single source of truth for project metadata and tool configurations (replaces setup.py).
11. Modular Design: Group code by domain (e.g., /users, /payments) rather than function (e.g., utils.py) to maintain separation of concerns.
# Author
[@sjdev](https://sjdev.co)
&lt;[sj@sjdev.co](mailto:sj@sjdev.berlin)&gt;
# License
MIT. See [LICENSE](../LICENSE).

139
LLM_DEV_PROMPTS/README.md Normal file
View File

@@ -0,0 +1,139 @@
# LLM Development Prompts
An MIT-licensed collection of LLM prompts by [@sjdev](https://sjdev.co), intended for use in bootstrapping new projects or building out new features in existing codebases.
The prompts set forth best practices and procedural directives that must be followed in architecture and development. Think of this repo as akin to the Chicago Manual of Style, AP Stylebook (for journalism), or The ALWD Guide to Legal Citation.
The prompts include 1) general repository development standards and 2) language and framework-specific development directives, intended to be reviewed by the code-building model at the outset work and followed throughout the works progress. For example, the Javascript code styleguide sets forth the simple directive: “[u]se const for all declarations, unless you need to reassign, then use let. Never use var.”
These prompts are a work in progress. I add to them as I work on new projects in new languages and frameworks, and I am also still in the process of memorializing prompts relating to languages and frameworks I have used for many years.
# Usage - generally
Imagine the scenario: you, as a developer, are tasked with adding a new feature to an existing codebase, with maximum automation via LLM code generation. At the outset, before adressing the substantive implementation, you would run the following prompt (discussed in greater detail below):
Read $TD/prompts/REPO_POLICIES.md and $TD/prompts/EXISTING_REPO_CHECKLIST.md, then bring this repo up to those
standards. Your scope is repo scaffolding and policy compliance: Makefile, Dockerfile, .dockerignore, .gitignore, .editorconfig, CI
workflow, README sections, LICENSE, REPO_POLICIES.md, and any language-specific config files (.golangci.yml, .prettierrc, etc.).
## Quick Start - optional scripts for cli agent
### Existing Repo
Run from within the repo you want to bring up to standard. Clone the prompts repo once, then run both commands in order.
```bash
export TD="$(mktemp -d)"
git clone --depth 1 https://github.com/kjannette/LLM_DEV_PROMPTS.git "$TD"
```
**Repository structure and policies:**
```bash
claude "Read $TD/prompts/REPO_POLICIES.md and
$TD/prompts/EXISTING_REPO_CHECKLIST.md, then bring this repo up to those
standards. Your scope is repo scaffolding and policy compliance:
Makefile, Dockerfile, .dockerignore, .gitignore, .editorconfig, CI
workflow, README sections, LICENSE, REPO_POLICIES.md, and any
language-specific config files (.golangci.yml, .prettierrc, etc.).
You must also run the formatter (make fmt) and fix any linter errors
(make lint) so that make check passes — this will touch source code,
but do not restructure, refactor, or rewrite any application logic.
Follow the policies yourself: work on a feature branch, never git add -A,
and make each logical change a separate commit (e.g. one commit for
formatting, one for linter fixes, one for README updates, one for each
new repo file added, etc.)."
```
**Code style and conventions:**
```bash
claude "Read $TD/prompts/CODE_STYLEGUIDE.md and whichever
language-specific styleguides in $TD/prompts/ apply to this repo
(CODE_STYLEGUIDE_GO.md, CODE_STYLEGUIDE_JS.md, CODE_STYLEGUIDE_PYTHON.md,
GO_HTTP_SERVER_CONVENTIONS.md). Then review the application code in this
repo and bring it into compliance with those coding standards. Your scope
is application code structure and style: naming, patterns, error
handling, project layout, and conventions described in the styleguides.
Do not modify repo scaffolding (Makefile, Dockerfile, CI workflow,
.gitignore, .editorconfig, etc.) — only application code. Work on a
feature branch, never git add -A, and make each logical change a
separate commit."
```
### New Repo
Run from inside the directory where you want to create a new repo. Clone the
prompts repo once, then run both commands in order.
```bash
export TD="$(mktemp -d)"
git clone --depth 1 https://github.com/kjannette/LLM_DEV_PROMPTS.git "$TD"
```
**Repository scaffolding:**
```bash
claude "Read $TD/prompts/REPO_POLICIES.md and
$TD/prompts/NEW_REPO_CHECKLIST.md, then set up this new repo according
to those standards. Your scope is repo structure and required files:
README.md, LICENSE, REPO_POLICIES.md, Makefile, Dockerfile, .dockerignore,
.gitignore, .editorconfig, CI workflow, and language-specific config.
Run the formatter (make fmt) and fix any linter errors (make lint) so
that make check passes — this will touch source code, but do not
restructure, refactor, or rewrite any application logic. Follow the
policies yourself: work on a feature branch, never git add -A, and make
each logical change a separate commit (e.g. one commit for formatting,
one for linter fixes, one for README, one for each new repo file, etc.)."
```
**Code style and conventions:**
```bash
claude "Read $TD/prompts/CODE_STYLEGUIDE.md and whichever
language-specific styleguides in $TD/prompts/ apply to this repo
(CODE_STYLEGUIDE_GO.md, CODE_STYLEGUIDE_JS.md, CODE_STYLEGUIDE_PYTHON.md,
GO_HTTP_SERVER_CONVENTIONS.md). Then review the application code in this
repo and bring it into compliance with those coding standards. Your scope
is application code structure and style: naming, patterns, error
handling, project layout, and conventions described in the styleguides.
Do not modify repo scaffolding (Makefile, Dockerfile, CI workflow,
.gitignore, .editorconfig, etc.) — only application code. Work on a
feature branch, never git add -A, and make each logical change a
separate commit."
```
## Getting Started
```bash
git clone https://github.com/kjannette/LLM_DEV_PROMPTS.git
cd prompts
```
Prompts are stored as Markdown files in `prompts/`. Copy or reference them as
needed in your projects.
## Rationale
LLM prompts, especially development policies, benefit from version control and a
single authoritative source. This repo provides a central place to maintain,
share, and evolve prompts across projects.
## Design
The repository is a collection of Markdown files organized in the `prompts/`
subdirectory. Each file contains one or more related prompts or policy
documents. There is no build step or runtime component; the prompts are consumed
by copying them into other projects or referencing them directly.
## TODO
- Add more prompt templates for common development tasks
## License
MIT. See [LICENSE](LICENSE).
## Author
[@sjdev](https://sjdev.co)

View File

@@ -0,0 +1,214 @@
1. General Types
Don't ever use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are almost never used appropriately in JavaScript code.
/* WRONG */
function reverse(s: String): String;
Do use the types number, string, boolean, and symbol.
/* OK */
function reverse(s: string): string;
Instead of Object, use the non-primitive object type.
2. Use const and let
JavaScript first searches to see if a variable exists locally, then searches progressively in higher levels of scope until global variables. var is function scope, but, let and const are block scope.
Using let and const where appropriate makes the intention of the declarations clearer.
It will also help in identifying issues when a value is reassigned to a constant accidentally by throwing a compile time error.
Use a linter that automates checking and fixing this so that changing let to const doesn't become a delay in code review.
3. Use === instead of ==
JavaScript utilizes two different kinds of equality operators: === | !== and == | !=.
It is considered best practice to always use the former set when comparing.
If two operands are of the same type and value, then === produces true and !== produces false.
However, when working with == and !=, we'll run into issues when working with different types. In these cases, they'll try to coerce the values, unsuccessfully.
4. Use the fastest way to loop arrays
There are many ways to loop through array. The first way is a for loop. Other ways include the for...of loop, the forEach method for arrays, map, filter, and others. There is also the while loop.
The for loop is the fastest way. Caching the length makes the loop perform better. Some browser engines have optimized the for loop without manually caching the length property. The forEach is slower than the for loop, so it's probably better to avoid it, especially for large arrays. However, unless we are desperate for performance at the code level (which is rare), make it readable. For example, we can use the for loop in server-side applications and the array methods in client-side applications, because, in general, we don't have expensive operations on the client-side.
5. Prefer array methods
It is recommended to use a functional approach without intermediate variables. The base JavaScript for loop can be more performant in some browsers but the benefit can be measured only by iterating over millions of items. It is the job of compiler and runtime to remove the penalty of using new array methods.
6. Do not trust any data - Validate
Make sure that all the data that goes into our system is clean and exactly what we need. This is most important on the back end when writing out parameters retrieved from the URL.
The same applies to forms that validate only on the client side. Another very insecure practice is to read information from the DOM and use it without validation.
7. Generics
Don't ever have a generic type which doesn't use its type parameter.
8. Any
Don't use any as a type unless you are in the process of migrating a JavaScript project to TypeScript. The compiler effectively treats any as "please turn off type checking for this thing". It is similar to putting an @ts-ignore comment around every usage of the variable. This can be very helpful when you are first migrating a JavaScript project to TypeScript as you can set the type for stuff you haven't migrated yet as any, but in a full TypeScript project you are disabling type checking for any parts of your program that use it.
In cases where you don't know what type you want to accept, or when you want to accept anything because you will be blindly passing it through without interacting with it, you can use unknown.
9. Strict configuration
The stricter configuration is mandatory. Otherwise, types will be too permissive, and it is what we are trying to avoid as much as possible with Typescript.
{
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"strict": true,
"noUnusedLocals": true,
}
The most important one here is the strict flag which actually covers four other flags: noImplicitAny, noImplicitThis, alwaysStrict and strictNullChecks.
10. Callback Types - Return Types of Callbacks
Don't use the return type any for callbacks whose value will be ignored:
/* WRONG */
function fn(x: () => any) {
x();
}
Do use the return type void for callbacks whose value will be ignored:
/* OK */
function fn(x: () => void) {
x();
}
Why: Using void is safer because it prevents you from accidentally using the return value of x in an unchecked way:
function fn(x: () => void) {
var k = x(); // oops! meant to do something else
k.doSomething(); // error, but would be OK if the return type had been 'any'
}
11. Optional Parameters in Callbacks
Don't use optional parameters in callbacks unless you really mean it:
/* WRONG */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime?: number) => void): void;
}
This has a very specific meaning: the done callback might be invoked with 1 argument or might be invoked with 2 arguments. The author probably intended to say that the callback might not care about the elapsedTime parameter, but there's no need to make the parameter optional to accomplish this — it's always legal to provide a callback that accepts fewer arguments.
Do write callback parameters as non-optional:
/* OK */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime: number) => void): void;
}
12. Overloads and Callbacks
Don't write separate overloads that differ only on callback arity:
/* WRONG */
declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;
Do write a single overload using the maximum arity:
/* OK */
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;
Why: It's always legal for a callback to disregard a parameter, so there's no need for the shorter overload. Providing a shorter callback first allows incorrectly-typed functions to be passed in because they match the first overload.
13. Function Overloads
13.1 Ordering
Don't put more general overloads before more specific overloads:
/* WRONG */
declare function fn(x: unknown): unknown;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: unknown, wat?
Do sort overloads by putting the more general signatures after more specific signatures:
/* OK */
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: unknown): unknown;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: string, :)
Why: TypeScript chooses the first matching overload when resolving function calls. When an earlier overload is "more general" than a later one, the later one is effectively hidden and cannot be called.
13.2 Use Optional Parameters
Don't write several overloads that differ only in trailing parameters:
/* WRONG */
interface Example {
diff(one: string): number;
diff(one: string, two: string): number;
diff(one: string, two: string, three: boolean): number;
}
Do use optional parameters whenever possible:
/* OK */
interface Example {
diff(one: string, two?: string, three?: boolean): number;
}
Note that this collapsing should only occur when all overloads have the same return type.
Why: This is important for two reasons.
TypeScript resolves signature compatibility by seeing if any signature of the target can be invoked with the arguments of the source, and extraneous arguments are allowed. This code, for example, exposes a bug only when the signature is correctly written using optional parameters:
function fn(x: (a: string, b: number, c: number) => void) {}
var x: Example;
// When written with overloads, OK -- used first overload
// When written with optionals, correctly an error
fn(x.diff);
The second reason is when a consumer uses the "strict null checking" feature of TypeScript.
Because unspecified parameters appear as undefined in JavaScript, it's usually fine to pass an explicit undefined to a function with optional arguments. This code, for example, should be OK under strict nulls:
var x: Example;
// When written with overloads, incorrectly an error because of passing 'undefined' to 'string'
// When written with optionals, correctly OK
x.diff("something", true ? undefined : "hour");
13. Use Union Types
Don't write overloads that differ by type in only one argument position:
/* WRONG */
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}
Do use union types whenever possible:
/* OK */
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment;
}
Note that we didn't make b optional here because the return types of the signatures differ.
❔ Why: This is important for people who are "passing through" a value to your function:
function fn(x: string): Moment;
function fn(x: number): Moment;
function fn(x: number | string) {
// When written with separate overloads, incorrectly an error
// When written with union types, correctly OK
return moment().utcOffset(x);
}

View File

@@ -0,0 +1,5 @@
{
"devDependencies": {
"prettier": "3.8.1"
}
}

View File

@@ -84,7 +84,7 @@ func (h *StripeHandler) CreateCheckoutSession(w http.ResponseWriter, r *http.Req
Quantity: stripe.Int64(1), Quantity: stripe.Int64(1),
}, },
}, },
SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=success&session_id={CHECKOUT_SESSION_ID}"), SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe/return/{CHECKOUT_SESSION_ID}"),
CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"), CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"),
ClientReferenceID: stripe.String(userID), ClientReferenceID: stripe.String(userID),
CustomerEmail: stripe.String(user.Email), CustomerEmail: stripe.String(user.Email),
@@ -246,7 +246,7 @@ func (h *StripeHandler) CreateOnboardingCheckout(w http.ResponseWriter, r *http.
Quantity: stripe.Int64(1), Quantity: stripe.Int64(1),
}, },
}, },
SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=success&session_id={CHECKOUT_SESSION_ID}"), SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe/return/{CHECKOUT_SESSION_ID}"),
CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"), CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"),
CustomerEmail: stripe.String(body.Email), CustomerEmail: stripe.String(body.Email),
} }

View File

@@ -4,6 +4,7 @@ import Navbar from "./components/Navbar";
import Login from "./pages/login/Login"; import Login from "./pages/login/Login";
import Signup from "./pages/Signup"; import Signup from "./pages/Signup";
import Subscribe from "./pages/subscribe/Subscribe"; import Subscribe from "./pages/subscribe/Subscribe";
import CheckoutReturn from "./pages/subscribe/CheckoutReturn";
import Addresses from "./pages/addresses/Addresses"; import Addresses from "./pages/addresses/Addresses";
import Alerts from "./pages/alerts/Alerts"; import Alerts from "./pages/alerts/Alerts";
import AlertHistory from "./pages/alertHistory/AlertHistory"; import AlertHistory from "./pages/alertHistory/AlertHistory";
@@ -20,6 +21,7 @@ export default function App() {
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} /> <Route path="/signup" element={<Signup />} />
<Route path="/subscribe" element={<Subscribe />} /> <Route path="/subscribe" element={<Subscribe />} />
<Route path="/subscribe/return/:sessionId" element={<CheckoutReturn />} />
<Route path="*" element={<Navigate to="/login" />} /> <Route path="*" element={<Navigate to="/login" />} />
</Routes> </Routes>
); );
@@ -29,6 +31,7 @@ export default function App() {
return ( return (
<Routes> <Routes>
<Route path="/subscribe" element={<Subscribe />} /> <Route path="/subscribe" element={<Subscribe />} />
<Route path="/subscribe/return/:sessionId" element={<CheckoutReturn />} />
<Route path="/account" element={<><Navbar /><Account /></>} /> <Route path="/account" element={<><Navbar /><Account /></>} />
<Route path="*" element={<Navigate to="/subscribe" />} /> <Route path="*" element={<Navigate to="/subscribe" />} />
</Routes> </Routes>
@@ -45,6 +48,7 @@ export default function App() {
<Route path="/alertevents" element={<AlertHistory />} /> <Route path="/alertevents" element={<AlertHistory />} />
<Route path="/account" element={<Account />} /> <Route path="/account" element={<Account />} />
<Route path="/subscribe" element={<Subscribe />} /> <Route path="/subscribe" element={<Subscribe />} />
<Route path="/subscribe/return/:sessionId" element={<CheckoutReturn />} />
<Route path="*" element={<Navigate to="/addresses" />} /> <Route path="*" element={<Navigate to="/addresses" />} />
</Routes> </Routes>
</div> </div>

View File

@@ -0,0 +1,54 @@
import { useState, useRef } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { verifyCheckoutSession } from "../../api/stripe";
import { useAuth } from "../../contexts/AuthContext";
import "./Subscribe.css";
export default function CheckoutReturn() {
const { sessionId } = useParams();
const navigate = useNavigate();
const { refreshAccount } = useAuth();
const [error, setError] = useState(null);
const started = useRef(false);
if (sessionId && !started.current) {
started.current = true;
verifyCheckoutSession(sessionId)
.then(() => refreshAccount())
.then(() => navigate("/addresses", { replace: true }))
.catch((err) => setError(err.message || "Payment verification failed"));
}
if (!sessionId) {
return (
<div className="subscribe">
<div className="subscribe__container">
<h1 className="subscribe__title">Koin Ping</h1>
<div className="alert alert--error">No session ID found.</div>
<p><a href="/subscribe">Back to Subscribe</a></p>
</div>
</div>
);
}
if (error) {
return (
<div className="subscribe">
<div className="subscribe__container">
<h1 className="subscribe__title">Koin Ping</h1>
<div className="alert alert--error">{error}</div>
<p><a href="/subscribe">Try again</a></p>
</div>
</div>
);
}
return (
<div className="subscribe">
<div className="subscribe__container">
<h1 className="subscribe__title">Koin Ping</h1>
<p>Verifying your payment...</p>
</div>
</div>
);
}

View File

@@ -1,10 +1,9 @@
import { useState, useEffect } from "react"; import { useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { useAuth } from "../../contexts/AuthContext"; import { useAuth } from "../../contexts/AuthContext";
import { useUserProperties } from "../../contexts/UserPropertiesContext"; import { useUserProperties } from "../../contexts/UserPropertiesContext";
import { import {
createOnboardingCheckout, createCheckoutSession,
verifyCheckoutSession,
activateFreeTier, activateFreeTier,
} from "../../api/stripe"; } from "../../api/stripe";
import Input from "../../components/Input"; import Input from "../../components/Input";
@@ -15,159 +14,89 @@ import "./Subscribe.css";
const STEPS = ["Create Account", "Choose Plan"]; const STEPS = ["Create Account", "Choose Plan"];
export default function Subscribe() { export default function Subscribe() {
const { currentUser, signup } = useAuth();
const { state: userProps, dispatch, ACTION_TYPES } = useUserProperties();
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const { currentUser, signup, refreshAccount } = useAuth();
const hasPaymentReturn = searchParams.get("payment") === "success"; const queryParameters = new URLSearchParams(window.location.search)
const [step, setStep] = useState(hasPaymentReturn || currentUser ? 2 : 1); const success = queryParameters?.get("payment")
const [loading, setLoading] = useState(hasPaymentReturn); const session_id = queryParameters?.get("session_id")
const [error, setError] = useState("");
console.log('success, session_id ------>', success, session_id)
function forward(success, session_id) {
success && session_id ?
navigate('/addresses') : console.log('ewps')
}
setTimeout(forward, 2000, success, session_id);
const [step, setStep] = useState(currentUser ? 2 : 1);
const [data, setData] = useState({ const [data, setData] = useState({
selectedTier: "", email: currentUser?.email || "",
email: "",
password: "", password: "",
confirmPassword: "", confirmPassword: "",
selectedTier: null,
}); });
const [error, setError] = useState(
searchParams.get("payment") === "cancelled"
? "Payment was cancelled. Please try again."
: ""
);
const [loading, setLoading] = useState(false);
function set(field, value) { function set(field, value) {
setData((prev) => ({ ...prev, [field]: value })); setData((prev) => ({ ...prev, [field]: value }));
} }
// Handle Stripe payment returns async function handleStep1() {
useEffect(() => {
const payment = searchParams.get("payment");
const sessionId = searchParams.get("session_id");
const signupLockKey = sessionId
? `kp_onboard_signup_started_${sessionId}`
: null;
if (payment === "cancelled") {
if (signupLockKey) sessionStorage.removeItem(signupLockKey);
sessionStorage.removeItem("kp_onboard_email");
sessionStorage.removeItem("kp_onboard_pw");
setSearchParams({}, { replace: true });
setStep(2);
setError("Payment was cancelled. Please try again.");
return;
}
if (payment !== "success" || !sessionId) return;
// Phase 1: no account yet — create it. Auth state change remounts the
// component (App.jsx swaps route trees) and Phase 2 runs on the next mount.
if (!currentUser) {
const savedEmail = sessionStorage.getItem("kp_onboard_email");
const savedPw = sessionStorage.getItem("kp_onboard_pw");
if (!savedEmail || !savedPw) {
if (signupLockKey) sessionStorage.removeItem(signupLockKey);
setSearchParams({}, { replace: true });
setError("Session expired. Please start the signup process again.");
setStep(1);
setLoading(false);
return;
}
// Guard against duplicate signup attempts caused by repeated effects.
if (signupLockKey && sessionStorage.getItem(signupLockKey) === "1") {
setLoading(true);
return;
}
setLoading(true);
if (signupLockKey) sessionStorage.setItem(signupLockKey, "1");
signup(savedEmail, savedPw).catch((err) => {
if (signupLockKey) sessionStorage.removeItem(signupLockKey);
setSearchParams({}, { replace: true });
setError("Account creation failed: " + err.message);
setStep(1);
setLoading(false);
});
return;
}
// Phase 2: authenticated — verify the checkout and go to the dashboard.
setSearchParams({}, { replace: true });
setLoading(true);
dispatch({ type: ACTION_TYPES.CLEAR_USER_PROPERTIES });
verifyCheckoutSession(sessionId)
.then(() => {
navigate("/addresses", { replace: true });
})
.catch((err) => {
setError("Payment verification failed: " + err.message);
setStep(2);
})
.finally(() => setLoading(false));
}, [currentUser, searchParams, setSearchParams, signup, navigate, userProps, dispatch, ACTION_TYPES]);
// ── Step handlers ─────────────────────────────────────────────────────────
function handleStep1() {
setError(""); setError("");
if (!data.email || !data.password || !data.confirmPassword) { if (!data.email || !data.password) {
setError("Please fill in all fields"); setError("Email and password are required");
return;
}
if (data.password !== data.confirmPassword) {
setError("Passwords do not match");
return; return;
} }
if (data.password.length < 6) { if (data.password.length < 6) {
setError("Password must be at least 6 characters"); setError("Password must be at least 6 characters");
return; return;
} }
setStep(2); if (data.password !== data.confirmPassword) {
} setError("Passwords do not match");
async function handleStep2() {
setError("");
if (!data.selectedTier) {
setError("Please select a plan to continue");
return; return;
} }
try {
setLoading(true); setLoading(true);
try {
if (data.selectedTier === "free") {
if (!currentUser) {
await signup(data.email, data.password); await signup(data.email, data.password);
} setStep(2);
await activateFreeTier();
navigate("/addresses", { replace: true });
return;
}
sessionStorage.setItem("kp_onboard_email", data.email);
sessionStorage.setItem("kp_onboard_pw", data.password);
dispatch({
type: ACTION_TYPES.SET_USER_PROPERTIES,
payload: { email: data.email, password: data.password },
});
const { url } = await createOnboardingCheckout(
data.email,
data.selectedTier,
);
window.location.href = url;
} catch (err) { } catch (err) {
if (err.code === "auth/email-already-in-use") { setError(err.message || "Failed to create account");
setError("Email already in use. Try logging in instead.");
} else if (err.code === "auth/invalid-email") {
setError("Invalid email address");
} else if (err.code === "auth/weak-password") {
setError("Password is too weak");
} else {
setError("Failed to process plan selection: " + err.message);
}
} finally { } finally {
setLoading(false); setLoading(false);
} }
} }
// ── Progress bar ────────────────────────────────────────────────────────── async function handleStep2() {
setError("");
if (!data.selectedTier) {
setError("Please select a plan");
return;
}
setLoading(true);
try {
if (data.selectedTier === "free") {
await activateFreeTier();
await refreshAccount();
navigate("/addresses", { replace: true });
} else {
const { url } = await createCheckoutSession(data.selectedTier);
window.location.href = url;
}
} catch (err) {
setError(err.message || "Something went wrong");
setLoading(false);
}
}
function ProgressBar() { function ProgressBar() {
return ( return (
@@ -186,8 +115,7 @@ export default function Subscribe() {
<div key={label} className="progress-bar__step"> <div key={label} className="progress-bar__step">
{i > 0 && ( {i > 0 && (
<div <div
className={`progress-bar__connector ${ className={`progress-bar__connector ${done || active
done || active
? "progress-bar__connector--active" ? "progress-bar__connector--active"
: "progress-bar__connector--inactive" : "progress-bar__connector--inactive"
}`} }`}
@@ -198,8 +126,7 @@ export default function Subscribe() {
{done ? "\u2713" : stepNum} {done ? "\u2713" : stepNum}
</div> </div>
<div <div
className={`progress-bar__label ${ className={`progress-bar__label ${active
active
? "progress-bar__label--active" ? "progress-bar__label--active"
: "progress-bar__label--inactive" : "progress-bar__label--inactive"
}`} }`}