Ctrl K
ComunidadGuías…

AGENTS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# AGENTS.md

## 1. Project Overview
Pastebox is a lightweight self-hosted Go service for curl-friendly text sharing.

Core storage model:
- The default `local` backend stores paste content as files and metadata as JSON sidecars.
- The optional `mysql` backend stores compressed paste chunks, metadata, admin accounts, sessions, and settings in MySQL/MariaDB.
- SQLite stores admin/session/settings data in local mode and migration completion markers in MySQL mode.
- Do not move paste content into SQLite unless explicitly requested.

The service targets simple Docker operation, minimal dependencies, and a dark UI optimized for text-based workflows.

## 2. Directory Structure
```text
pastebox/
├── .github/                 # Dependabot, validation, image publish, release
├── cmd/server/              # HTTP startup, lifecycle, feature handlers and tests
│   ├── admin.go             # Admin routing, actions, flash and audit helpers
│   ├── admin_auth.go        # Setup, login, logout and reset
│   ├── admin_list.go        # List presentation, filtering, sorting, pagination
│   ├── main.go
│   ├── server.go
│   ├── routes.go
│   └── handler_*.go
├── internal/                # Local/MySQL storage, auth persistence and tests
├── locales/                 # en.json and ko.json
├── templates/               # External HTML, css/ and self-hosted js/
├── tests/browser/           # Playwright regression tests and isolated server harness
├── Dockerfile
├── docker-compose.yml
├── docker-entrypoint.sh
├── go.mod / go.sum
├── AGENTS.md / WORK.md
└── README*, API*, DATA_POLICY* # English/Korean documentation
```

## 3. Architecture
- HTTP startup and lifecycle: `cmd/server/main.go`, `cmd/server/server.go`; routing: `cmd/server/routes.go`.
- HTTP features: `handler_*.go`; administrator actions: `admin.go`, authentication: `admin_auth.go`, list/filter/pagination: `admin_list.go`.
- Storage dispatch: `internal/store.go`; local persistence: `store_local.go`; MySQL chunks: `mysql_store.go`; admin/session/settings persistence: `admin_store.go`, `admin_mysql_store.go`.
- HTTP header timeout is 10 seconds and idle timeout is 60 seconds. Keep body/response deadlines unset for large streaming transfers. SIGINT/SIGTERM drains requests for up to 30 seconds; Compose allows 40 seconds before forced termination.

Typical runtime layout:
```text
/paste-data/<code>
/paste-data/<code>.json
/paste-data/pastebox.db
```
Host mapping is typically `./data:/paste-data`.

## 4. Critical Rules
- E2EE was intentionally removed. Do not reintroduce E2EE (flags, key files, encrypted metadata, encrypt/decrypt helpers) unless explicitly requested.
- Generated paste passwords must never include `=`.
  - Keep special set compatible with URL query use.
- HTML must remain in external template files under `templates/`; do not inline large templates in Go code.
- Preserve fine-grained per-paste locking in the store layer; do not replace with a single global lock.
- When asked which files were modified, respond only with files changed in the most recent edit scope.
- When committing, exclude `WORK.md` unless the user explicitly asks to include it.
- When `AGENTS.md` is modified, always include it in the relevant commit rather than leaving it uncommitted.
- When committing, add a `Co-authored-by: Codex <codex@openai.com>` trailer unless the user explicitly asks not to.
- If a change scope would be split into 2 or more commits, ask before pushing; otherwise commit only unless the user explicitly says to push.
- When the user requests multiple distinct tasks and authorizes pushing them, finish and commit each task separately, run the final combined validation, and then push all resulting commits together once. Do not push each task immediately after its commit unless the user explicitly requests per-task pushes.
- When modifying markdown files, include the filename in the commit subject or body so the changed docs are obvious.
- Keep commit messages themselves in English and follow the existing short conventional style.
- Before starting work, always consult `WORK.md` alongside `AGENTS.md` so recent task history is part of the working context.
- After any commit+push sequence, record in `WORK.md` what work was done and what mostly changed, using the existing date-based log format, and include the commit ID plus commit message when available.
- If a follow-up fix is needed because of the agent's own mistake, record that in `WORK.md` as well, including what was wrong and how it was corrected.
- In `WORK.md`, every work-entry line under a date heading must start with `- ` so dated blocks are easy to scan.
- When a problem, regression, or unexpected behavior is reported or discovered, inspect the relevant git history before fixing it. Use commands such as `git log --follow -p -- <file>` and `git blame <file>` to identify when the affected code changed and what part changed.
- When fixing code after an error occurs, record the problematic code area, the root cause, and the fix in `AGENTS.md`, including a small relevant code snippet when useful.
- Before making a similar future change, review those recorded error-fix notes to avoid repeating the same mistake.

## 5. Upload Behavior
Supported upload methods:
```bash
echo "hello" | curl -X POST --data-binary @- http://localhost:8080/
curl -F "file=@test.txt" http://localhost:8080/
```

Text-only policy:
- Allow text-oriented types/extensions (logs, source files, markdown, config formats, etc.).
- Block binary/media/archive/image uploads.

Detection and memory rule:
- Use leading-sample text detection.
- Do not load full file contents into memory for large uploads.
- Avoid full-file `io.ReadAll(reader)` upload paths.

Limits and metadata:
- Upload size limit is 1 GiB.
- Exceeding limit must return `413 Payload Too Large`.
- Multipart uploads must store original filename in metadata.
- Raw-body uploads preserve an explicit `filename` header or `Content-Disposition` filename; otherwise the filename stays empty.
- Admin list and paste view should display filename when present (fallback `-` in admin list).

## 6. Upload Options and Responses
Policies:
- Temporary (default): expires by configured TTL.
- Permanent: `data-policy: permanent` (no automatic expiration).
- One-time: `data-policy: once` (deleted after first successful view).
- Password-protected: `usepassword: true` with an 8-character generated password.

Password access methods:
- Query: `?password=<password>`
- Header: `paste-password: <password>`

Custom code:
- Supplied through `code` header.
- Max length: 10 characters.
- Allowed chars only: `A-Z`, `a-z`, `0-9`, `_`, `-`.
- Duplicate code must return `409 Conflict`.

Successful upload response should include:
- `url`
- `manage`
- `expires` (when applicable; omitted for permanent)
- `password` (when applicable)

Upload and clone responses must not issue a separate delete URL. New clients should delete with the manage token through the management API.

Response formats:
- Upload success and upload error responses may return JSON when `?format=json` is present.
- Raw paste responses use `?raw=1` on the paste URL.

Delete token handling:
- Never log delete tokens or manage tokens.
- Never expose delete tokens in admin UI.

## 7. Paste Viewing
Paste view (`templates/paste.html`) must include:
- Paste code
- Original filename (when present)
- Language badge
- Copy button
- Clone button
- Line number toggle
- Raw button
- Syntax highlighting
- Horizontal scrolling for long lines

Behavior rules:
- Copy must copy only paste content, never line numbers.
- Raw mode (`?raw=1`) must return original text content, not the HTML viewer.
- Line numbers must be disabled by default.
- When enabled, line numbers must always stay on the left.
- Do not reintroduce right-side line-number layout regressions.
- `.log` files should display language as `logs` (not `plaintext`).
- Markdown must be shown as highlighted code, not rendered preview.

## 8. Clone and Management Behavior
- Clone must be triggered from the paste view page UX.
- Do not require users to manually use `/clone` or `?clone=1` as a primary UX.
- Clone must preserve original filename.
- Clone must be blocked when uploads are disabled.
- Clone result page (`templates/clone.html`) should show URL, expiration (if any), generated password (if any), and manage URL.
- Every successful upload and clone also produces a private manage URL using `?manage=<token>`.
- Manage page (`templates/manage.html`) should allow viewing paste metadata, copying the public/manage URLs, enabling or disabling password protection, changing the retention policy, and deleting the paste.
- The manage page delete button must send `DELETE /api/v1/pastes/<code>` with the manage token in the `paste-manage-token` header. Do not restore the old POST form action.
- Disabling password protection from the manage page requires the current password.
- When password protection is enabled from the manage page, the generated password must be shown once and then only stored hashed.

## 9. Admin Behavior
Admin entry point: `/admin`.

Admin account rules:
- If no admin exists, the first created account becomes the only administrator account.
- Additional admin registration must remain disabled afterward.
- Admin passwords must never be stored in plain text.
- Admin sessions use the selected backend (SQLite locally, MySQL in DB mode), are issued as HttpOnly cookies, and expire after 24 hours.
- Setup, login, and reset forms are CSRF-protected.
- Admin login and reset attempts are rate limited.

Upload disable behavior:
- State must be persisted in the selected admin backend.
- When uploads are disabled:
  - New uploads are rejected.
  - Clone is rejected.
  - Existing paste view and deletion still work.
  - Admin page still works.

Delete controls:
- Individual delete uses the current confirmation UX, including the administrator preference to skip subsequent single-delete confirmations.
- Bulk delete must keep a confirmation popup and clear irreversible warning.
- Delete result messages must not use query params like `/admin?deleted=...`.
- Use internal flash messages (for example short-lived HttpOnly cookie) for delete results.
- Admin delete should support both a single paste and a multi-select bulk delete action.

Required admin copy:
- Setup page: `The first account becomes the only administrator account.`
- Login page: `Sign in with the administrator account to manage stored pastes.`
- Keep setup and login copy distinct.

Admin list timestamps should be converted to local time before rendering.

Admin update check:
- The admin page should show both the running Pastebox version and the latest GitHub Release version with their respective short Git commit IDs.
- The latest version must come from the public GitHub Releases API for `kmw0410/pastebox`.
- Resolve the latest release commit from the Git ref for the release tag; do not treat `target_commitish` as a commit SHA.
- Docker release builds must inject both the workflow-generated release tag and the short commit ID into the server binary.
- A failed release lookup must not block the admin page; show an unavailable state and keep the existing admin functions working.
- Cache release lookup results to avoid unnecessary GitHub API requests.

## 10. Language and Localization
- UI language is selected with `LANGUAGE` env var.
- Supported values: `en`, `ko`.
- Missing/empty/invalid values must fallback to `en`.

Locale files:
- `locales/en.json` is the default English source.
- `locales/ko.json` may provide Korean translations.
- Missing or empty Korean values must fallback to English.

API documentation:
- Keep the English HTTP API reference in `API.md` and the Korean reference in `API_ko.md`.
- Keep both API documents behaviorally aligned with the implemented handlers.
- `README.md` should link to `API.md`, and `README_ko.md` should link to `API_ko.md`.

Template translation:
- Templates should use `{{ t "key" }}`.
- Go server must register `t` when parsing templates.

Korean wording rule:
- Do not translate “paste” as `붙여넣기`.
- Prefer domain wording like `Paste`, `Paste 링크`, `저장된 Paste`, `Paste 열기`, `Paste 삭제`.

## 11. Docker and Runtime Configuration
Dockerfile/runtime expectations:
- Dockerfile should copy `templates/`, `locales/`, and `docker-entrypoint.sh`.
- Image must include `locales/`.
- `docker-compose.yml` runs the published `ghcr.io/kmw0410/pastebox:latest` image.
- For local builds, use `docker build`; no separate Docker Hub Compose file is maintained.
- Copy both `go.mod` and `go.sum`, download and verify modules, and build with `-mod=readonly`. Do not regenerate dependencies with `go mod tidy` inside the image build.
- CI reads the exact Go version from the Docker builder; update the Dockerfile to change the deployment and validation toolchain together.

Compose rule:
- `LANGUAGE` must be under `environment`, not at service root.

`MIRROR_URL` rules:
- Intended for published-container runtime usage, currently GHCR.
- It only changes `/etc/apk/repositories` inside the running container.
- It does not affect registry image pulls.
- It does not affect apk commands already executed during build.
- If empty, entrypoint must not modify repositories.

Entrypoint responsibilities:
- Create `DATA_DIR`.
- Fix bind-mount permissions.
- Optionally apply `MIRROR_URL`.
- Optionally apply timezone settings.
- Drop privileges using `su-exec`.

Timezone rules:
- Default timezone should be `Asia/Seoul`.
- `TZ` should be optionally overrideable.
- On valid `TZ`, apply `/usr/share/zoneinfo/$TZ` to `/etc/localtime` and write `$TZ` to `/etc/timezone`.
- On invalid `TZ`, log warning and keep existing timezone state.

UI consistency rules:
- Expanded index page design must not regress to older simple layout.
- Index page must include:
  - text-only notice
  - text upload example
  - file upload example
  - password-protected example
  - data-policy section
  - expiration information section
  - manual deletion section
  - Home button
  - GitHub button
  - Copy curl example button
- Template styling should use the current external CSS files under `templates/css/`:
  - `templates/css/common.css` for shared layout, variables, panels, forms, and buttons.
  - `templates/css/pages.css` for index/admin/password/clone/manage/404 page layouts.
  - `templates/css/paste.css` for the paste viewer and paste toolbar.
- New UI work should follow and extend the existing dark CSS class system.
- Admin/password templates should remain visually aligned with the dark UI system.
- Password page must remain centered.
- Admin stats and upload-control cards should keep intended dark card style.

## 12. GitHub Actions
Docker publish workflow requirements:
- Push tags/images:
  - `ghcr.io/kmw0410/pastebox:latest`
  - `ghcr.io/kmw0410/pastebox:vYY.MM.DD`
- Pass the generated release tag and short commit ID into the Docker build so the admin page can report the running build precisely.
- If same date tag exists, append incrementing suffix (`-1`, `-2`, ...).
- Multi-arch support must include:
  - `linux/amd64`
  - `linux/arm64`
- `paths-ignore` may ignore workflow files, Markdown files, and image files.

## 13. Security Rules
Never log secrets:
- Paste passwords
- Delete tokens
- Manage tokens
- Admin passwords
- Admin reset tokens
- Admin session tokens

The initial admin setup token is intentionally printed at startup so the operator can bootstrap the service. The Compose reset-token default and this bootstrap log are intentional project decisions; preserve them unless explicitly asked to change them. All other logs contain operational metadata only (IDs, counters, failures, blocked reasons), without secret material.

Use security headers where appropriate:
- `X-Frame-Options: DENY`
- `Referrer-Policy: no-referrer`
- `X-Content-Type-Options: nosniff`
- `Cache-Control: no-store`

## 14. Testing Commands
```bash
# Basic upload
echo "hello" | curl -X POST --data-binary @- http://localhost:8080/

# Multipart upload
echo "hello file" > test.txt
curl -F "file=@test.txt" http://localhost:8080/

# Permanent / one-time
curl -H "data-policy: permanent" -F "file=@test.txt" http://localhost:8080/
curl -H "data-policy: once" -F "file=@test.txt" http://localhost:8080/

# Password-protected
curl -H "usepassword: true" -F "file=@secret.txt" http://localhost:8080/

# Custom code
curl -H "code: my-log" -F "file=@test.txt" http://localhost:8080/

# Raw view / password view
curl "http://localhost:8080/<code>?raw=1"
curl -H "paste-password: <password>" http://localhost:8080/<code>

# Delete through the management API
curl -X DELETE -H "paste-manage-token: <manage-token>" http://localhost:8080/api/v1/pastes/<code>

# Binary block test (expect 415)
head -c 1024 /dev/urandom > binary.bin
curl -i -F "file=@binary.bin" http://localhost:8080/

# Upload limit test (expect 413)
truncate -s 1025M over_1gb.log
curl -i -F "file=@over_1gb.log" http://localhost:8080/

# Mirror check
docker exec pastebox cat /etc/apk/repositories
```

## 15. Common Mistakes to Avoid
- Putting `LANGUAGE` at service root instead of under `environment`.
- Reintroducing E2EE behavior.
- Letting generated passwords include `=`.
- Regressing index page to old simple layout.
- Breaking line-number layout so numbers appear on the right.
- Logging secret fields other than the intentional initial admin setup-token log.
- Removing `MIRROR_URL` runtime override behavior for published-container usage.
- Replacing per-paste lock strategy with coarse global lock.
- Switching upload validation back to full-file memory reads.

## 16. Detailed Work Log
- Each dated `WORK.md` block must use at least two lines.
  - Each work-entry line under the date heading must start with `- `.
  - Line 1 should summarize what changed.
  - Line 2 should spell out the concrete code/file or behavior changes.
  - If a commit+push happened, add the commit ID and commit message on the same dated block.
  - If the same calendar date already exists, append the new notes under that date instead of creating a duplicate date heading.
- Main page design refresh:
  - Reworked index layout into expanded dark card system (`#111111`, `#E4E4E4`, accent palette), removed outer border/glow, increased width (`max-w-[98rem]`), tuned subtitle scale, and balanced top/bottom grid spacing.
- Admin/password template alignment:
  - Unified structure to dark card language, adjusted button and card behavior, ensured password page centering.
- Admin copy key cleanup:
  - Unified admin page title/description keys and removed redundant brand output in admin headers.
- Paste view and line-number alignment:
  - Added prototype work and fixed line-number baseline alignment with highlight interaction adjustments.
  - Preserved left-side line-number layout and prevented right-side regression.
- Output snapshot folder workflow:
  - Added curated design/output copies and removed non-final artifacts from the snapshot set.
- Timezone runtime improvements:
  - Added default `Asia/Seoul`, optional `TZ` override, timezone application logic in entrypoint, and warning behavior for invalid timezone values.
- Server-side local time conversion:
  - Converted admin list timestamps to local time before rendering.
- Original filename support:
  - Preserved multipart original filename in metadata and reflected it in admin and paste UI; clone preserves filename.
- Admin delete controls and flash messages:
  - Kept per-item and bulk delete behavior aligned with UX rules; delete outcomes use internal flash-message mechanics instead of query parameters.
- Localization stability:
  - Preserved JSON locale workflow (`en` default + `ko` fallback behavior) and restored localized index/admin text behavior where needed.
- Docker Hub runtime mirror override:
  - Kept runtime-only `MIRROR_URL` repository rewrite behavior in entrypoint.

## 17. Error Fix Notes
Use this section to record recurring lessons from error-driven code fixes. Each note should include:
- Problematic code area: file/function or behavior that failed.
- Cause: why the error happened.
- Fix: how it was corrected, with a small code snippet when helpful.
- Prevention: what to check before making similar changes again.

- Problematic code area: `.github/workflows/docker-publish.yml`, `release.yml`, and `validate.yml` CI trust boundaries.
  Cause: Checkout persisted a workflow-capable token across external actions, action versions used mutable tags, and release tags were filtered only by `^v` before interpolation into shell scripts. Git permits tag names containing shell substitutions. Validation also lacked a known-vulnerability check.
  Fix: Pin external actions to verified full commit SHAs, disable checkout credential persistence, expose `WORKFLOW_TOKEN` only to the tag-push step through a temporary credential helper, use `GITHUB_TOKEN` for releases, pass tags through `env` and validate the date-tag format, restrict publishing to main and trusted workflow runs, and gate deployment with pinned govulncheck.
  Prevention: Keep secrets out of persistent Git configuration, never interpolate tag/event values into `run` scripts, validate release tags as `^v[0-9]{2}[.][0-9]{2}[.][0-9]{2}(-[1-9][0-9]*)?$`, and preserve vulnerability checking in the reusable validation workflow.

- Problematic code area: `.github/workflows/validate.yml` MySQL service port expression.
  Cause: The integration job introduced in `0f37d8c` indexed the ports object with the number `3306`; GitHub Actions object keys require a string, which actionlint rejects.
  Fix: Use `job.services.database.ports['3306']` for the dynamically mapped port.
  Prevention: Run actionlint when changing workflow expressions, including existing reusable jobs.

- Problematic code area: `Dockerfile` Go builder and the standard library embedded in the server binary.
  Cause: The builder remained pinned to Go 1.27.1 after security fixes were released for multipart header parsing, HTTP Range processing, and certificate verification. Runtime Alpine package upgrades do not replace the statically linked Go standard library.
  Fix: Build the server with the patched Go toolchain:
  ```dockerfile
  FROM golang:1.27.3-alpine3.24 AS builder
  ```
  Prevention: Audit the Docker builder's Go version rather than the host Go version, run vulnerability checks with the builder toolchain for both supported architectures, and rebuild and redeploy the image after a Go security update.

- Problematic code area: `cmd/server/main_test.go` / `TestUploadHandlerCustomDataPolicyDuration`.
  Cause: The upload response formats `expires` with `time.RFC3339`, which has second precision, while the first test bounds compared against nanosecond-precision `time.Now()` values.
  Fix: Compare against second-aligned bounds with a small upper allowance:
  ```go
  minExpires := before.Add(time.Hour).Truncate(time.Second)
  maxExpires := after.Add(time.Hour).Truncate(time.Second).Add(time.Second)
  ```
  Prevention: When testing user-visible timestamps, align assertions to the timestamp format precision before comparing parsed values.

- Problematic code area: `templates/paste.html` clone form and `cmd/server/handler_clone.go` password failure path.
  Cause: The paste view clone form posted to the same paste path without an explicit clone marker, and a protected paste clone attempt with a missing password fell through to a generic password failure instead of a password form that submits back to the clone action.
  Fix: Mark the clone form action explicitly and render the password page with a POST clone action when a browser clone request needs a password:
  ```html
  <form method="post" action="/{{ .ID }}?clone=1">
  ```
  ```go
  a.renderPasswordPage(w, r, id, "/"+id+"?clone=1", http.MethodPost, a.localizedText("paste_clone", "Clone"))
  ```
  Prevention: When adding paste-page actions that can require password authentication, make the intended action explicit in the form URL and ensure the password prompt submits back to that same action.

- Problematic code area: `cmd/server/handler_upload.go` JSON upload/clone response compatibility for `new-paste-password`.
  Cause: A new CLI could send the custom-password header to an older server that ignored it yet still returned a successful unprotected upload, leaving the client unable to distinguish protection from silent downgrade.
  Fix: Include an explicit protection confirmation in every JSON upload and clone response:
  ```go
  PasswordProtected: meta.PasswordHash != "",
  ```
  Prevention: When a client depends on a newly introduced security header, add an explicit response field that proves the server applied it, and test older responses that omit the field.

- Problematic code area: `cmd/server/handler_paste.go` / `readPastePreview`.
  Cause: Repeated `utf8.Valid` scans while removing one trailing byte made malformed input beyond the 64 KiB upload sample take quadratic time.
  Fix: Decode the bounded preview once, replace malformed bytes with ASCII `?`, and omit an incomplete trailing rune at the byte limit. Preserve raw bytes and streaming line counting.
  Prevention: Treat content after the upload sample as untrusted UTF-8; test internal invalid bytes and truncated runes, and benchmark increasing preview sizes without timing assertions.

- Problematic code area: HTTP routing in `cmd/server/main.go` and `cmd/server/routes.go`.
  Cause: Cache and Referrer protections existed only in API/health response writers, leaving sensitive browser and text responses unprotected.
  Fix: Apply `Cache-Control: no-store` and `Referrer-Policy: no-referrer` before ServeMux routing, including errors, redirects, and HEAD; retain static CSS/JS cache policies.
  Prevention: Test response headers through the production HTTP handler, including authenticated content and token-bearing results, and check canonical-path redirects and static caching.

- Problematic code area: `cmd/server/version.go` release lookup.
  Cause: The admin page required both the GitHub Releases response and a second tag-ref request to succeed within a three-second timeout; a slow or failed tag lookup hid an otherwise valid latest release.
  Fix: Use a ten-second request timeout and retain the latest release version and URL when only its commit lookup fails:
  ```go
  return release.TagName, "", release.HTMLURL, true, nil
  ```
  Prevention: Test partial GitHub API failures separately from a failed latest-release request, so version availability does not depend on optional commit metadata.

- Problematic code area: `cmd/server/main_test.go` / `TestPasteTemplateShowsFullLoadConfirmationForTruncatedContent` expiration assertion.
  Cause: HTML template rendering escapes the `+` in an RFC 3339 timezone offset, so an assertion for the unescaped rendered timestamp failed.
  Fix: Assert the translated expiration label and timestamp prefix instead of the HTML-escaped offset:
  ```go
  "paste_expires:", "2026-09-12T12:00:00"
  ```
  Prevention: When checking text emitted into HTML, assert semantic output or account for contextual escaping instead of comparing raw source strings.

- Problematic code area: `templates/index.html` homepage command examples.
  Cause: Adding a Copy button to every command made the compact curl reference visually noisy, beyond the intended single top-level copy action.
  Fix: Keep only the required `Copy curl example` button and render each data-policy command as its own labeled code block.
  Prevention: Preserve the established visual density of the homepage; add controls per example only when the request explicitly calls for them.

- Problematic code area: `cmd/server/main.go` HTTP lifecycle and `Dockerfile` dependency installation.
  Cause: The default HTTP server had no header/idle limits or signal-driven drain, and image builds regenerated dependencies instead of using the checked-in go.sum. CI selected a different toolchain from the deployment builder.
  Fix: Use an explicit server with header/idle limits and bounded graceful shutdown; copy and verify go.sum, build read-only, and derive CI's Go version from Dockerfile.
  Prevention: Test in-flight and stalled requests during shutdown; preserve streaming body behavior and keep deployment and validation toolchains aligned.

- Problematic code area: README.md and README_ko.md runtime documentation insertion.
  Cause: Matching `## ` as a substring also matched a `### Paste` preview heading and split its Markdown marker.
  Fix: Restore `### Paste` and place the runtime paragraph before the explicit Healthcheck heading.
  Prevention: Match complete Markdown headings and inspect the rendered structure or diff after scripted documentation edits.

- Problematic code area: `templates/paste.html` truncated-paste full loading.
  Cause: Since the preview feature introduced in `13ac26a`, every truncated response emitted `remainingPasteContent`, even though only one-time pastes include embedded remaining text. JavaScript interpreted the empty element as the full-load source and skipped the raw request for ordinary pastes.
  Fix: Emit the embedded template only for `{{ if and .Truncated .OneTime }}`; ordinary pastes then use the existing authenticated raw-fetch path.
  Prevention: Run browser coverage for both ordinary and one-time truncated pastes, verifying full content after confirmation, not only the button or template markup.

## 18. Commit Message Examples
Use short, conventional commit messages:
```text
feat: add language support
feat: show original filenames
docs: update AGENTS.md rules
feat: add admin delete controls
fix: keep line numbers on the left
fix: restore localized index page
perf: stream uploads after text sample validation
```

## 19. Final Guidance
Keep Pastebox:
- simple
- file-based
- curl-friendly
- Docker-friendly
- easy to self-host
- minimal in dependencies

When editing, preserve:
- file-based paste storage
- JSON sidecar metadata
- SQLite scope limited to local admin/session/settings/upload-disable and migration markers; preserve the existing optional MySQL backend
- external templates under `templates/`
- text-only upload policy
- 1 GiB upload limit
- custom code validation rules
- secure secret-handling and no-secret logs
- stable left-aligned line-number layout
- clone UX from paste view page
- language fallback correctness
- Docker runtime mirror/timezone behavior

## 20. CLI and Validation
- The CLI is maintained in the separate `kmw0410/pastebox-cli` repository, linked from README.md and README_ko.md. Do not recreate a nested CLI module or packaging pipeline here unless requested.
- `.github/workflows/validate.yml` runs for pull requests and is reused as the Docker Publish gate. Validation runs on GitHub-hosted runners; publishing remains on the self-hosted runner.
- Validate with `go test -race ./...` and `go vet ./...` using the Docker builder's Go version.
- MySQL/MariaDB integration tests use `PASTEBOX_TEST_MYSQL_DSN`, create a uniquely named temporary database, and drop only that database. The test account needs CREATE/DROP DATABASE permissions. Without that variable the integration test is skipped.
- Browser tests live in `tests/browser/`, separate from runtime dependencies. Run `npm ci`, `npx playwright install chromium`, and `npm test` there; the harness builds a server with isolated temporary data on port 18080.
- Keep browser coverage for search, left-side line numbers, line links, content-only copying, confirmed full loading, and one-time pastes.