Capabilities and permissions
A slop can take a photo, record audio, or turn a saved recording into text. Declare
the devices and host services it needs in slop.ts; request them from a button in
the editor. Declaring a capability lets the app ask. The person’s system or browser
permission still decides whether it can proceed.
Declare what your app uses
Section titled “Declare what your app uses”import { defineSlop } from "hitslop";
export default defineSlop({ slug: "capture-notes", title: "Capture Notes", description: "Keep photos, recordings, and transcripts in one document.", author: { name: "Your Name" }, categories: ["productivity"], window: { kind: "standard", width: 560, height: 680 }, theme: { paper: "#fff7e6", ink: "#382d24" }, capabilities: ["camera", "microphone", "speech"],});Use only the entries your app needs. Omitting capabilities declares none.
| Capability | How to use it | What it admits |
|---|---|---|
camera |
navigator.mediaDevices.getUserMedia({ video: true }) |
Camera capture in the interactive editor. |
microphone |
navigator.mediaDevices.getUserMedia({ audio: true }) |
Microphone capture, including recording with MediaRecorder. |
speech |
speech.transcribe(attachment, options) from hitslop/svelte |
On-device transcription of a recording already stored in the document. |
A combined audio/video request needs both camera and microphone. Transcribing an
imported recording needs speech, but not microphone. Playing music through Web Audio
or an <audio> element needs neither. Start audio playback from a user gesture.
These are the three supported declaration values. File selection, attachment storage, HTTPS data and media, and embedded HTTPS frames do not need an additional capability; see files and the web. Browser policies and CORS still apply.
Where they work
Section titled “Where they work”| Environment | Camera and microphone | speech.transcribe |
|---|---|---|
| Mac app, interactive document | Declared devices, subject to macOS permission. | On-device recognition when this Mac supports the requested language and permission is granted. |
slop dev preview |
Declared devices, subject to browser support and permission. | Unavailable: reason: "host". Test in the Mac app. |
| Local Chrome browser copy | Declared devices, subject to browser support and permission. | Unavailable: reason: "host". |
| Native icon, preview, or export renderer | Refused. Render saved content instead. | Unavailable. |
In the Mac app, a capture request must come from the current document’s main page in a visible window. Embedded web frames cannot borrow its device access. A document being closed cannot start a new device request. Saved-state rendering leaves the live editor’s device access available.
macOS permissions belong to hitSlop, not to an individual .slop file. There is no
separate per-slop consent dialog or permission manager yet. The declaration is an
additional restriction on which slops may request a device; it is not a per-document
permission grant. To change a system decision, open System Settings → Privacy &
Security → Camera, Microphone, or Speech Recognition.
Capture with the web APIs
Section titled “Capture with the web APIs”Camera and microphone capture use normal browser APIs, not a hitSlop wrapper:
// Call from a Start camera button. Use { audio: true } for the microphone,// or { video: true, audio: true } when both are declared.const stream = await navigator.mediaDevices.getUserMedia({ video: true });video.srcObject = stream; // a <video autoplay muted playsinline> elementawait video.play();
// When Stop is pressed, the page is hidden, or the component is destroyed:for (const track of stream.getTracks()) track.stop();video.srcObject = null;Check that navigator.mediaDevices?.getUserMedia exists before offering capture. Handle
NotAllowedError with a permission message and NotFoundError with a missing-device
message. Keep an explicit capturing state, prevent duplicate starts, and stop a stream
that arrives after the person cancels or closes the UI. Never request a device on mount.
Use canvas to turn a camera frame into a Blob. Use MediaRecorder to record a stream;
check MediaRecorder.isTypeSupported() before choosing a MIME type, and wait for its
final dataavailable/stop events before combining chunks. Show recording state and a
Stop button. Keep streams and unfinished chunks in local view state.
A finished Blob becomes durable only when imported as an attachment and referenced by
the document. Wrap it in a File with an appropriate name and MIME type. Each attachment
is limited to 10 MiB; a document allows 100 MiB and 256 attachments. Bound recordings by
size and handle an import refusal without discarding the person’s recording.
Save a recording and transcribe it
Section titled “Save a recording and transcribe it”This minimal document keeps one recording and an editable transcript:
import { defineDocument, s } from "hitslop";
export default defineDocument({ recording: s.optional(s.string()), transcript: s.text(),});Use these functions from the editor’s Keep recording and Transcribe buttons. The file
may come from a file picker or a finished MediaRecorder recording:
Put this in the editor component’s script so getDocument runs during component initialization:
import { attachments, getDocument, isSpeechError, speech } from "hitslop/svelte";import schema from "./schema";const doc = getDocument(schema);
async function keepRecording(file: File) { await attachments.import<typeof schema.descriptor>(file, (tx, ref) => { tx.fields.recording.set(ref.id); tx.fields.transcript.set(""); });}
async function transcribe(): Promise<string> { const id = doc.current.recording; if (!id) return "Keep a recording first."; try { const { text } = await speech.transcribe(id, { locale: "en-US" }); // Do not put an earlier recording's result on a newly selected recording. if (doc.current.recording !== id) return "The recording changed."; await doc.fields.transcript.set(text); return "Transcript added."; } catch (error) { if (!isSpeechError(error)) throw error; return error.message; }}Show the returned message and disable Transcribe while it is running. A transcript is
returned text, not an automatic edit: save it through the document handles, as above.
Use attachments.url(id) for playback. The same API also accepts the attachment
reference returned by attachments.import instead of its ID.
Omit locale to use the person’s default recognition language. The result includes
text and the locale used. Locale separators are normalized from _ to - before
the host receives the request. Recognition runs on the Mac, with no server fallback. If the
language cannot be recognized on-device, hitSlop reports it as unavailable before asking
for speech permission. Adding a Dictation language in System Settings may help, but
availability depends on the Mac and its installed language support.
The Mac provider accepts m4a, mp3, wav, flac, mp4, and mov recordings. Other formats,
including Ogg and WebM, return unavailable with reason format without asking for
speech permission. They can still be stored and played where supported. Let the host
report format support instead of maintaining a format list in your app. Renaming a
file does not convert it: hitSlop checks its bytes. A transcript is limited to 1 MiB.
Handle speech outcomes
Section titled “Handle speech outcomes”Use isSpeechError(error) rather than instanceof; the host and authored app are
separate bundles.
| Code | Meaning and response |
|---|---|
unavailable |
Inspect reason: notDeclared, host, system, locale, permission, or format. Explain the limit and keep recording/playback usable. |
cancelled |
The page was replaced, reloaded, or closed. Keep the recording; the person can retry. |
failed |
The recording could not be read or recognized. Preserve it and show the message. |
Speech recognition is separate from microphone permission. It can fail even when audio
recording works. An older host without this service returns unavailable with host.
Build a new app version to change capabilities
Section titled “Build a new app version to change capabilities”Capabilities are part of the embedded app, not editable document data. Change slop.ts,
rebuild and register the template, then create a new document to try the new declaration.
Rebuilding or reinstalling the Mac app does not upgrade the app inside an existing
document. See build, register, and share.
What may come later
Section titled “What may come later”The current API stops at camera, microphone, and speech. These are directions to
explore, not supported options or release commitments:
- Permissions tied to the app’s code, with a new decision after a remix or upgrade, rather than relying only on hitSlop-wide OS consent.
- Additional devices such as MIDI, after their declaration and permission model is designed.
- Packaged worker and audio-worklet entry points for libraries that need them. Today, blob/data executable code remains blocked; bundle-compatible libraries are required.
Browser transcription and a newer native speech backend are not implemented. Keep a useful path without those services. The availability page separates current platform support from broader future work.