Back to Funclet
SOURCE / PINNED RELEASE

Made of little things.

Funclet

Release
0b497cfc2f66…
Author-recorded commit
1e531b24f490…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-6b7a37cac1d

Archive hash verified: 71d97a89ed8c40c2…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

docs/funclet-brief.md
# Funclet

A compact Wavefunc radio receiver: find a station, listen, see what is playing,
and revisit favourite stations. This is a port of the relevant domain formats
from `/Users/schlaus/workspace/wavefunc-rewrite`, not its application infrastructure.

## Scope and boundaries

| Feature | Source | Napplet boundary |
| --- | --- | --- |
| Stations and station search | kind 31237, `useStations.ts` | OUTBOX query with Wavefunc relay hint and NIP-50 search |
| Radio playback | Wavefunc stream tags/content | MEDIA shell-owned live session, HTTPS audio only |
| Music search | `contextvm/server.ts` | CVM `search_artists`, `search_recordings`, `search_releases` |
| Now playing | `extract_stream_metadata` | CVM, bounded polling for the active station |
| Current listener | account store | optional IDENTITY and its change subscription |
| Existing favourites | kind 30078, label `wavefunc_user_favourite_list` | optional OUTBOX reads for the current identity |
| Artwork | thumbnail tag | optional RESOURCE, bounded image bytes and revoked object URLs |
| Preferences | player settings | optional CONFIG with local defaults |
| HTTP-only stations | original station stream URLs | optional LINK opening on an explicit click; copyable URL if absent/denied |

Required domains: `outbox`, `media`. CVM is optional so a temporary absence does
not prevent radio playback; its search and metadata enhancements explain their
unavailable state. IDENTITY, CONFIG, RESOURCE and LINK are also optional. No RELAY escape
hatches, app-owned signer, transport, external media elements, or durable browser
storage. No cross-napplet integration or new protocol fields.

Production CVM public key: `bb0707242a17a4be881919b3dcfea63f42aacedc3ff898a66be30af195ff32b2`.
Transport hints: `wss://relay.wavefunc.live`, `wss://relay2.contextvm.org/`.
The shell controls routing and authorization. CVM calls deny automatic payment.
Music results are catalogue metadata, not playable audio sources.

## Compatibility decision

Wavefunc favourites are custom kind-30078 events, with an arbitrary `d`, a
`wavefunc_user_favourite_list` label, metadata JSON and station `a` tags. The installed
host reserves kind 30078 for its scoped `soy.app-data/1` validator. IDENTITY is
read-only, and LISTS does not advertise this custom format. Existing lists can be
read; compatible writes require host support. The choice between retaining full
compatibility and separate supported NIP-51 favourites was resolved by the user:
favourites are read-only in this version. Do not rewrite or migrate these records.

## Visual direction

A playful miniature broadcast sculpture, carrying the supplied Wavefunc website's
cream paper, heavy ink outlines, slanted uppercase type, red switches and cyan signal
accents into real Three.js geometry. The main experience is a bevelled receiver with
speaker, inset signal screen, play key, rotary volume control and an elevated [WF]
cube. Directional light, contact shadows, extruded lettering and a custom signal
shader make the hardware feel physical. Meshes and canvas textures are generated
locally; no model/font/network loaders or remote render assets.

The cube rotates slowly at rest and faster during playback, independently of subtle
automatic camera drift and mouse parallax. Shader motion illustrates
broadcast activity, not measured audio levels (the host owns the audio). Reduced
motion freezes continuous animation. Hidden and undersized scenes stop rendering.
GPU resources, pointer capture and animation frames are cleaned up on teardown.
WebGL failure retains the complete accessible HTML player and station browser.

The directory resembles a stack of station tickets; selected-station information
belongs to the same printed console. Real mesh controls support pointer interaction;
visible HTML controls provide keyboard, touch and assistive-technology alternatives.
A 3D view toggle opens station information without losing playback.

App-owned palette in both host themes and without THEME: paper #fef9ea, ink #1d1c13,
red #c5001c, cyan #00c8d8. No duplicated website navigation or remote fonts.
Desktop: sculpture/player left, station directory right. Portrait: compact sculpture,
player and scrollable station tickets. Short landscape: side-by-side controls. Below
280×260: resize notice plus active playback control. All controls remain reachable.

Proof: actual station playback, moving cube, mesh play and volume interactions,
idle/playback rotation, reduced-motion stillness, shader compilation, no-WebGL fallback; actual frames
200×160, 320×560, 900×600, full screen and touch 390×844, 360×640, 844×390. Refresh the
landscape cover and capture portrait evidence from the hosted build.

## Failure and lifecycle behavior

Distinguish incomplete station/list queries from empty results. Use request epochs
to discard stale searches, identity reads and metadata after a station switch.
Do not mark a favourite saved before acknowledged success. Preserve the current
station while searching. Explain unsupported stream formats, missing identity,
host denial and CVM failure. Stop polling when paused/hidden; close subscriptions,
media sessions, CVM state and artwork handles on teardown.

## HTTP stream fallback

Keep MEDIA restricted to supported HTTPS streams. Mark stations without a playable
HTTPS stream but with a valid credential-free HTTP stream as external; show an
inline explanation and explicit Open in Wavefunc action next to the selected row
and in station details. Use optional LINK via the SDK; retain a selectable URL
at all times, including when LINK is absent, denied or fails. The current soyLI
LINK implementation only accepts HTTPS, so the button opens the existing
`https://wavefunc.live/station/<event-id>` route; it does not attempt to open the
raw HTTP stream or claim that a browser can play it. Prefer the station's primary HTTP stream,
never rewrite its scheme, and never silently open a link on station selection.
Keep the active station playing until a successful handoff, then pause that same
session if it is still current. Preserve the printed Wavefunc visual direction.

The taller receiver face reserves separate space for the display, control labels,
play key and volume dial. Case lettering uses transparent texture ink rather
than rectangular background patches; all labels remain on the physical surface.

## Mechanical control feedback

Hovering the 3D volume dial or range input and scrolling changes volume in five-percent steps (up increases, down decreases), bounded to 0–100. The 3D dial also supports dragging in one-percent detents. Dial changes make a short, bright mechanical click; play/pause presses make a deeper spring-key chunk. Feedback is synthesized locally with short noise envelopes and a low resonant body, uses no external assets, and degrades silently when browser audio is unavailable. Rapid detents are rate-limited to prevent harsh overlapping clicks. Volume is restored and saved through optional NAP-STORAGE (`receiver-volume`, a number from 0 to 1 encoded as a string). Failed or absent storage keeps the session usable; serialized writes preserve the latest gesture and a late restore cannot overwrite it. Existing art direction, layout, theme policy and hard NAP requirements remain intact.

The selected station address is saved under `receiver-station`. On reload, restore volume first, resolve that address through OUTBOX (using current station/stream data), and request playback through MEDIA. Browser gesture policies still apply. Missing/deleted stations and storage failures leave browsing available; late restores never replace a newer user selection.