| Age | Commit message (Collapse) | Author |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Task: 7b
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Add Oxanium-Regular.woff2 and Oxanium-Bold.woff2 (latin + latin-ext
subset, ~22 KB total) by Tyler Finck, fetched 2026-05-01 from
https://gwfh.mranftl.com/fonts/oxanium (mirrors Google Fonts);
released under SIL OFL 1.1.
- biomech/theme.css: add @font-face blocks pointing at the local
files. The CSS already declared 'Oxanium' as the body font but
the family was never loaded, so it silently fell back to system
sans-serif. Now the geometric techy face actually renders.
- biomech/FONT_LICENSE.txt: full attribution + OFL link + source
URL + fetch date so future maintainers can re-fetch deterministically.
- templates/embed.go: append themes/*/*.woff2 to the //go:embed glob
(deferred from task ra because go:embed requires every pattern to
match at least one file, which biomech now satisfies).
- README.md: append biomech entry to the bundled-fonts bullet list.
Self-hosted, no third-party CDN requests at page load; verified via
grep -RnE 'googleapis|gstatic|@import url\(http' against both source
and generated dist.
Amp-Thread-ID: https://ampcode.com/threads/T-019de000-c062-73c5-9afc-c9af422473cf
Co-authored-by: Amp <amp@ampcode.com>
|
|
(task ra)
- dos theme: bundle 'WebPlus IBM VGA 8x16' (.woff) by VileR from
the Ultimate Oldschool PC Font Pack v2.2 (CC BY-SA 4.0); replace
the never-loaded VT323 reference with @font-face + a self-hosted
bitmap face matching the BBS/DOS aesthetic.
- templates/embed.go: add ThemeExtraFiles + extend the //go:embed
glob with themes/*/*.woff and themes/*/FONT_LICENSE.txt so any
theme can ship its own font assets without further code changes;
add a NOTE comment block documenting the per-theme directory
convention and the //go:embed pattern-matching constraint.
- generator/writeThemeAsset: copy any extra files (fonts, license
notices) verbatim from the embedded FS into dist/themes/<name>/.
- README.md: rewrite the 'Third-party assets' section into a bullet
list with self-hosting policy, attribution for the bundled dos
font, and a step-by-step recipe for adding new bundled fonts.
- dos/FONT_LICENSE.txt: full attribution + CC BY-SA 4.0 notice.
Self-hosted, no third-party CDN requests at page load.
Amp-Thread-ID: https://ampcode.com/threads/T-019de000-c062-73c5-9afc-c9af422473cf
Co-authored-by: Amp <amp@ampcode.com>
|
|
Made-with: Cursor
|
|
|
|
When removing markdown inbox extras (embedded local images), os.Remove errors
were silently ignored. If removal failed, the image remained in the inbox and
was later published as a standalone image post.
- Check os.Remove errors and return a wrapped error.
- Rollback the already-saved post directory when extra removal fails.
- Deduplicate extras to avoid failing on duplicate references (e.g. same image
referenced twice in one markdown).
- Add a negative test that verifies the error is returned and no post is
persisted when removing an embedded image fails.
Fixes duplicate image posts caused by leftover inbox extras.
|
|
The test loops over all registered themes and runs the full pipeline for
each one, which makes it too slow for quick test cycles. Add a
`testing.Short()` guard so it returns early when -short is passed.
|
|
- loadAllPosts: pre-allocate posts slice with capacity len(entries) since
each directory entry may become a post.
- paginate: pre-allocate pages slice with capacity (len(posts)+pageSize-1)/pageSize.
The concat function no longer exists in the codebase (removed when theme
sound data was moved to embedded JSON files in commit 06dd860).
|
|
|
|
- Generate sounds.json for each theme inside templates/themes/<name>/
- Load sound data at runtime from embedded FS via templates.ThemeSounds()
- Replace ~1100 lines of hardcoded builder functions and presets with a
145-line loader (soundCache, initSoundCache, loadThemeSounds)
- Update embed.go to include themes/*/sounds.json in the embedded FS
- Update tests to use loadThemeSounds() instead of themeSoundPresets
- Eliminates duplication between themeSoundPresets and JSON output
Improves cohesion and DRY by keeping theme assets (CSS, JS, meta, sounds)
in a single location per theme directory.
|
|
|
|
atom.Generate, syncOutput)
- generator.Run(ctx, cfg) – ctx passed through to atom.Generate
- processor.Run(ctx, cfg) – signature updated for cancellation propagation
- atom.Generate(ctx, posts, cfg) – accepts ctx for future cancellation
- syncOutput(ctx, cfg) – rsync subprocesses now use exec.CommandContext
- Updated all call sites in tests, cmd/snonux/main.go, and integration tests
- All call sites pass context.Background() / context.TODO()
All tests pass: go test ./...
|
|
- Added SyncTargets and SyncRemoteDir to internal/config.Config.
- Replaced the package-level syncTargets and syncRemoteDir constants in
cmd/snonux/sync.go with default values, populated lazily via
resolveSyncConfig().
- Accept --sync-targets and --sync-remote-dir CLI flags; fall back to
SNONUX_SYNC_TARGETS and SNONUX_SYNC_REMOTE_DIR env vars; then fall back
to the previous hardcoded defaults (pi0/pi1, /var/www/html/snonux/).
|
|
Replace the large, duplicate switch statements in planPost and commitPlan
with a PostBuilder interface registered per file extension.
- PostBuilder interface: Plan(srcPath, ext) (postPlan, error) and
Commit(plan, postDir, id, now) (*post.Post, []string, error)
- Per-type builders: txtBuilder, mdBuilder, imageBuilder, audioBuilder.
- Each builder self-registers via init() into a map[string]PostBuilder.
- Core processor loops are now extension-agnostic, satisfying OCP/DIP.
- All existing tests pass.
|
|
Split the overloaded parseFlags into focused helpers:
- parseFlags now does *only* CLI flag parsing.
- resolvePaths handles ~ expansion.
- validateDirs performs filesystem creation/checks.
- resolveTheme selects the random theme using rng.
This removes the SRP violation in parseFlags and makes each
function independently testable. Tests updated accordingly.
Fixes: z8
|
|
Introduce postPlan to capture everything validated in Phase 1 before
any filesystem mutation occurs. Phase 1 scans the inbox, validates all
source files (parses text, markdown, image, audio), checks markdown
image claims for conflicts, and collects a plan per item. Phase 2
commits mutations only after every plan is validated: creates post
directories, writes assets, persists post.json, and removes sources.
If Phase 1 fails (e.g. unsupported file, missing markdown image, claim
conflict), no mutations occur and the inbox is left untouched. Roll
back the partial post directory if a mutation fails during commit.
Also refactor image and audio sub-processors into validation-only and
write-only parts (validateImage/writeImageAsset, validateAudio/copyFile)
so that Phase 1 is strictly read-only.
All existing tests pass.
|
|
|
|
|
|
The pre-scan claimedByMarkdown used to mark an image as claimed by any
markdown that referenced it, but it did not prevent two different markdown
files from referencing the same image. When that happened, the first file
processed would copy the image into its post directory and then delete it
from the inbox. The second file then failed because the source image was
already gone.
Fix: make image claiming exclusive. claimedByMarkdown now tracks the
owning markdown filename for each claimed image and returns an error
immediately during the pre-scan if a conflict is detected. This way no
sources are removed and no partial posts are created.
Also add tests:
- twoMarkdownsClaimingSameImageFails (negative)
- duplicateImageClaimsInSameMarkdownAllowed (positive)
|
|
findLocalImages used filepath.Base(ref) after stat succeeded, which
caused subdirectory or parent-directory references to pass the scan but
then fail during copy because the basename was looked up in the wrong
directory.
Fix: add isSimpleImageRef helper that accepts only flat filenames (no
path separators, no .. traversal). findLocalImages now returns the ref
unchanged, matching what copyLocalImages and claimedByMarkdown expect.
Added tests for isSimpleImageRef and negative findLocalImages cases for
subdirectory and parent-directory references.
|
|
Previously, uniqueID(postsDir, t) returned only a string and looped
forever when os.Stat returned an error other than IsNotExist
(e.g. permission denied). Change the signature to (string, error),
propagate the error, and update the caller in processFile to handle
it. Add positive and negative tests covering new ID generation,
suffix collision, and permission-based stat failure.
|
|
|
|
Fixes data loss risk in writePage:
- Write to a temp file instead of the target path.
- Explicitly close the temp file and check the error (was ignored).
- Rename to the final path only after successful close.
- Remove the temp file on any error so truncated output is never left on disk.
Added TestWritePage (happy path + template error) and
TestWritePage_tempFileCleanedOnError to verify no corruption of existing file.
|
|
- New blank mode hides all UI except the WebGL canvas; toggled via
the ‘blank’ nav button or the b key.
- Theme switching is now in-place (no page reload), avoiding the
Web Audio context reset that used to kill ambient music.
- Nukem ambient/riff rebuilt around the classic Grabbag riff (E5
E5 G5 A5 …).
|
|
- New theme directory: templates/themes/nukem/ (meta.json, theme.css, theme.js)
- Red/gold/black color palette with monospace typography
- WebGL: rising sparks, falling debris, rubble blocks, explosion glow
- Sound preset: 140 BPM E-minor power riffs with fanfare stabs (wild: 180 BPM thrash)
- All nav/open/close/scroll/wild/page effects implemented
Amp-Thread-ID: https://ampcode.com/threads/T-019dcd42-8558-748b-8c28-3f848400026d
Co-authored-by: Amp <amp@ampcode.com>
|
|
Complete rewrite of all 19 theme ambient presets to eliminate the
slow waltz/lo-fi feel. Every preset now targets a driving rock/
industrial/hardstyle vibe with:
- **Much faster BPM**: normal 120–180, wild 180–260 (was 60–70 / 120–140)
- **Power chords** replace major/minor triads — aggressive distortion-ready
- **Straight eighth-note drive** replaces swung waltz melodies — punchy,
rhythmic, no floaty legato
- **More active drum patterns**: double-kick, snare backbeats,
sixteenth-note hats, clap stabs on the 16th
- **Shorter attack / release** on normal (0.3→0.02, 0.5→0.15) so everything
hits harder
- **Higher filter cutoffs** for a brighter, more aggressive timbre
- **Three drone voices** (was 4) with lower fundamental octave, fewer
frequencies, more focused. Sub-bass added via buildRockSong octave divider.
- **buildRockSong** replaces buildSong: adds sub-bass (/2 voice) and bass
hits per measure, making each bar feel like a down-beat impact
- **buildDriveMelody** replaces buildSwingMelody: straight eighth notes, no swing
- **buildStabMelody** for wild mode: short 16th-note bursts with gaps
Also bumped test BPM cap from 250→400 to accommodate the new range.
|
|
|
|
Audit found universal mobile issues in every theme + shared CSS:
- body/overlay height:100vh had no 100dvh dynamic-viewport fallback on any theme
- @media(max-width:640px) was too minimal; 3 themes had no .content/.modal-inner reduction
- .modal-close buttons had zero padding → tap target well below 44×44px
- .transmit-btn and .page-nav a vertical height too small on most themes
Fixes applied:
Go/CSS common (shared.css):
- Add .modal-close padding + min 44×44px touch target
- Add overflow:hidden to #sno-wild-banner to prevent wild-mode banner overflow
- Add mobile @media override block with !important for .modal-inner padding,
.transmit-btn, and .page-nav a min-height to guarantee 44px touch targets
All 19 themes:
- Add height:100dvh fallback after height:100vh on body and .overlay
- Expand @media(max-width:640px) with missing .content/.modal-inner padding,
.transmit-btn min-height, and .modal-close touch-target padding where absent
Specific themes:
- DOS: enlarge .transmit-btn base padding from 4px to 8px
- Spaceage: bump .post-text from 0.86rem (~13.8px) to 0.9rem (~14.4px)
- Brutalist / Retrofuture / Synthwave: these previously had no .content or
.modal-inner reduction in their media queries; now filled in
|
|
|