52d5535Merge pull request #14 from notactuallytreyanastasio/blimp-core/handler-syntax
Add when guards, bubbles(), dot-notation actors
8eff600Add when guards, bubbles() annotation, dot-notation actor names
on :msg(args) when guard bubbles(Strategy) do. Dot-notation
actor names: actor Shop.Checkout do. 38 tree-sitter tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
433a23fAdd message send <- and orelse for bubble recovery
actor <- :message(args) for sending. orelse catches bubbles.
Precedence: orelse lowest, then send, then binary ops.
27 Zig tests, 39 tree-sitter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ce9b27bAdd situation keyword and _ as Hole
Hole is a first-class construct: identity at runtime, agent
directive via comments. situation is ambiguity-aware branching
where Holes are valid. 29 Zig tests, 38 tree-sitter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
c26666fMerge pull request #12 from notactuallytreyanastasio/blimp-core/situation-holes
Add situation keyword and _ as Hole
dc4dca1Add pipe operator |> (#11)
* Add typed state and tree-sitter corpus tests
Typed state syntax: state name: Type :: default
New :: token in lexer, type_name parsing for Int/String/[Item].
Backwards compatible with untyped state.
Tree-sitter corpus: 34 tests across actors, expressions,
collections, statements, and complete programs.
Zig parser: 21 inline tests. All green.
* Add pipe operator |> with left-associative chaining
Pipe parsing at lowest binary precedence. _ placeholder
in pipe calls filled by pipe value. 26 Zig tests, 38 tree-sitter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9e4556efeat: add Tauri desktop wrapper for term_diff
Bundle Phoenix LiveView as a native macOS app using Tauri v2
and Burrito. Sidecar pattern: Tauri spawns the Burrito binary,
polls localhost:4123 until Phoenix is ready, then navigates
the webview to the LiveView.
- Add Burrito dep and release config (macos_silicon target)
- Configure prod for desktop: port 4123, localhost, auto-start
- Remove force_ssl (desktop runs HTTP)
- Scaffold Tauri v2 project at desktop/src-tauri/
- Rust lifecycle: spawn sidecar on Ready, kill on ExitRequested
- Loading spinner while Phoenix boots
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
e5bc1ecAdd 26 LiveView integration tests on real git repos
Every test creates a tmp_dir git repo with commits and working tree
changes, mounts DiffLive pointed at it, and exercises the full flow.
Coverage:
- Navigation: j/k movement, Enter/q focus, Tab toggle, log view
- Stage/unstage: s stages, u unstages, rapid s/u/s/u, s from diff_view,
u on unstaged-only is no-op
- Commit mode: cc with nothing staged (error), cc with staged (editor),
Escape exits, empty message (error), valid message (creates real commit)
- Amend mode: a pre-populates message, shows amend diff, Escape/cancel
exits, submit updates real commit message
- Click to select, follow mode toggle, unknown keys survive,
rapid multi-key sequences don't crash
Also fixes diff parser crash on optional hunk header captures
(nil/empty string from regex groups).
183 total tests, 0 failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ea59594Fix amend mode: pre-populate message, show last commit diff
Three bugs fixed:
1. Textarea used value= attribute (ignored by browsers for textarea).
Changed to inner content: <textarea>{@commit.message}</textarea>
2. Amend mode now fetches and displays the diff of the commit being
amended in the right pane (full unified diff with hunks).
3. amend_diff cleared on cancel/escape/complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6d1db3anote that rev-parse is in fact just already plumbing
dc4c2d8note that rev-parse is in fact just already plumbing
9116bb9Fix amend mode: pre-populate message, show last commit diff
Three bugs fixed:
1. Textarea used value= attribute (ignored by browsers for textarea).
Changed to inner content: <textarea>{@commit.message}</textarea>
2. Amend mode now fetches and displays the diff of the commit being
amended in the right pane (full unified diff with hunks).
3. amend_diff cleared on cancel/escape/complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2f52939Add 26 LiveView integration tests on real git repos
Every test creates a tmp_dir git repo with commits and working tree
changes, mounts DiffLive pointed at it, and exercises the full flow.
Coverage:
- Navigation: j/k movement, Enter/q focus, Tab toggle, log view
- Stage/unstage: s stages, u unstages, rapid s/u/s/u, s from diff_view,
u on unstaged-only is no-op
- Commit mode: cc with nothing staged (error), cc with staged (editor),
Escape exits, empty message (error), valid message (creates real commit)
- Amend mode: a pre-populates message, shows amend diff, Escape/cancel
exits, submit updates real commit message
- Click to select, follow mode toggle, unknown keys survive,
rapid multi-key sequences don't crash
Also fixes diff parser crash on optional hunk header captures
(nil/empty string from regex groups).
183 total tests, 0 failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8ba4c79Add blog post: The Construction of Blimp
Second design journal entry walking through the grammar piece by piece.
Covers actors, typed state (Type :: default), Holes as first-class
agent directives, situation for ambiguity-aware branching, bubbles as
supervision actors, no shared memory, and supervisor-as-namespace with
both flat dot-notation and nested forms.
Also updates parser.md with decisions D4-D9 from this session.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
53d7404Riff on human-as-driver in blog post intro
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2b1f934WIP: Blimp grammar, lexer, parser, and tree-sitter grammar
Zig parser (chunks/lang/src/):
- token.zig: 50+ token types with keyword lookup
- lexer.zig: single-pass lexer, line/col tracking, 9 test blocks
- ast.zig: tagged union AST with 16 node types
- parser.zig: recursive descent with Pratt precedence, 8 test blocks
- main.zig: CLI reads .blimp files, prints S-expression AST
Tree-sitter grammar (chunks/lang/tree-sitter-blimp/):
- grammar.js: actors, state, on/become/reply, expressions
- highlights.scm: syntax highlighting queries
7 example .blimp programs all parse successfully.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
f305deeAdd note about diff viewer already existing, turtles all the way down
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
c0584edAdd blog post: The Construction of Blimp
Second design journal entry walking through the grammar piece by piece.
Covers actors, typed state (Type :: default), Holes as first-class
agent directives, situation for ambiguity-aware branching, bubbles as
supervision actors, no shared memory, and supervisor-as-namespace with
both flat dot-notation and nested forms.
Also updates parser.md with decisions D4-D9 from this session.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6604beaRiff on human-as-driver in blog post intro
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8335c10Watch .git/index and refs for stage/commit/branch changes
Watcher was ignoring ALL .git/ paths. Now watches specific git
internal files: index (stage/unstage), refs/ (commits/branches),
HEAD (checkout), MERGE_HEAD/REBASE_HEAD (merge/rebase).
External git operations (staging from another terminal, git hooks)
now trigger UI refresh automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4c7302dAdd FileState state machine for git staging lifecycle
Every file is in exactly one state (untracked, staged_new,
unstaged_modified, staged_modified, partial_modified, etc).
Each state returns the correct git command for stage/unstage
via stage_command/1 and unstage_command/1.
DiffLive now derives FileState from the file entry and delegates
to Runner.exec_file_command/2 instead of ad-hoc conditionals.
All Runner errors are logged and surfaced via put_flash.
24 new FileState tests. 142 total, 0 failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
69f47c7Update build-test skill for Elixir project quality cycle
Replaces cargo build/test with mix compile/test/credo pipeline.
Adds TDD reminder, frontmatter metadata, and mix.exs auto-discovery.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
84b75f5Extract CommitState pure state machine from Navigation
Moves commit workflow (idle->editing->submitting->complete/error) into
its own module with explicit phases. Navigation no longer tracks
commit_mode/amend_mode. Errors shown inline instead of flash messages.
Nothing-staged and no-previous-commit cases blocked at entry.
108 tests, 0 failures, 0 warnings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
f399bd4Modernize HEEx syntax and refactor submit_commit
Replace <%= if/for with :if/:for attributes across all templates.
Replace <%= expr %> with {expr} curly brace syntax throughout.
Break nested if-inside-case in submit_commit into separate functions:
execute_commit, run_git_commit, handle_commit_result.
Fix alias ordering (alphabetical within groups).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
98faa5aFix s/u keybindings: work from both file_list and diff_view
stage_selected and unstage_selected were restricted to :file_list
focus only. Now they fire from :diff_view too (anywhere a file is
selected). Guards on nil selected_file prevent empty commands.
Log_view/log_detail still no-op (no file context there).
6 new Navigation tests for s/u across focus states.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4a19037Add Elixir/HEEx style rules to CLAUDE.md
Codifies preferences from PR #1 review: HEEx curly brace syntax,
:if/:for attributes, no nested if/case, no Process.sleep in tests,
one type per file, embedded Ecto schemas, supervision patterns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
67f5c13Fix unstage on newly added files
git restore --staged fails on :added files (no prior version to
restore to). Now checks staged_status: :added files use
git rm --cached, :modified files use git restore --staged.
DiffLive passes the file's staged_status from RepoState.
Three new Runner tests covering the exact bug scenario.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5a7898aFix Runner stripping leading whitespace from git status output
String.trim() was removing the leading space from porcelain status
lines like " M file.ex", causing Status.parse to fail on files with
only unstaged changes. Changed to String.trim_trailing() to preserve
the significant leading column characters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
16392f7Add --json-schema support for forced structured JSON output
Claude.build_args now accepts :json_schema option, passing
--json-schema flag to claude -p for validated structured output.
Prompt module defines the review schema (summary + annotations array
with file, start_line, end_line, comment, severity enum).
Server's default_caller passes the schema automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9abc7cfFix Commentary.Server supervision and eliminate Process.sleep in tests
Replace async_nolink with start_child + explicit message passing.
Task sends {:review_complete, result} back to GenServer on completion.
Tests use assert_receive instead of Process.sleep for determinism.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
61f9a8dSplit Commentary.Types into one type per file
Annotation, ReviewResult, FileCommentary each get their own module
file under commentary/. Updated all alias references.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
d8440d2Migrate Runner from System.cmd to Erlang Port
All git commands now execute via Port.open with :spawn_executable.
Supports expected_exits option for commands like diff --no-index
that return exit code 1 on success. Adds 10s timeout.
Includes first Runner test suite (8 integration tests with tmp_dir).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
31d07c1Fix cursor drift after stage/unstage with path-based tracking
Navigation.sync_to_files/2 resolves file_index from selected_file
path instead of blindly trusting the index. After refresh, the
cursor stays on the same file even when the list reorders.
Removes ad-hoc selected_file resolution from keydown handler.
The refresh handler now syncs nav to the new file list properly.
6 new tests for sync_to_files covering: path resolution, file
removal, empty list, nil selected_file, list shrink clamping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13d528bConvert CommitState to embedded Ecto schema
Uses Ecto.Enum for phase and mode fields instead of plain @type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3e992e9Fix nav pipeline clobbering refreshed state after stage/unstage
handle_nav_change was using the pre-stage nav, overwriting the
freshly synced nav from refresh_now. Now re-reads socket.assigns.nav
after handle_stage_actions so downstream steps see the real state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2af65dbAdd plumbing_status/1 using diff-index, diff-files, ls-files
New Runner.plumbing_status/1 returns [FileState.t()] directly
from three plumbing commands instead of parsing porcelain output.
Runs alongside the existing status/1 for now.
9 integration tests covering all file states: untracked, staged_new,
unstaged_modified, staged_modified, partial_modified, staged_deleted,
unstaged_deleted, and mixed multi-file scenarios.
157 total tests, 0 failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
d152c17Remove LLMChain/CodeReviewer code entirely
Deleting the Ollama-based code reviewer and langchain dependency.
Will revisit AI review via claude -p CLI approach later.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
efcdeedFix stale state after stage/unstage: synchronous refresh
After a git stage/unstage command, immediately re-fetch repo state
instead of relying on async send(self(), :refresh). This prevents
the next keypress from seeing stale file states where a just-staged
file still appears as unstaged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9c373bcFix amend mode: pre-populate message, show last commit diff
Three bugs fixed:
1. Textarea used value= attribute (ignored by browsers for textarea).
Changed to inner content: <textarea>{@commit.message}</textarea>
2. Amend mode now fetches and displays the diff of the commit being
amended in the right pane (full unified diff with hunks).
3. amend_diff cleared on cancel/escape/complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0b8029cnote that rev-parse is in fact just already plumbing
cbc418dAdd 26 LiveView integration tests on real git repos
Every test creates a tmp_dir git repo with commits and working tree
changes, mounts DiffLive pointed at it, and exercises the full flow.
Coverage:
- Navigation: j/k movement, Enter/q focus, Tab toggle, log view
- Stage/unstage: s stages, u unstages, rapid s/u/s/u, s from diff_view,
u on unstaged-only is no-op
- Commit mode: cc with nothing staged (error), cc with staged (editor),
Escape exits, empty message (error), valid message (creates real commit)
- Amend mode: a pre-populates message, shows amend diff, Escape/cancel
exits, submit updates real commit message
- Click to select, follow mode toggle, unknown keys survive,
rapid multi-key sequences don't crash
Also fixes diff parser crash on optional hunk header captures
(nil/empty string from regex groups).
183 total tests, 0 failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
a2add80Fix nav pipeline clobbering refreshed state after stage/unstage
handle_nav_change was using the pre-stage nav, overwriting the
freshly synced nav from refresh_now. Now re-reads socket.assigns.nav
after handle_stage_actions so downstream steps see the real state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
046d9d6Watch .git/index and refs for stage/commit/branch changes
Watcher was ignoring ALL .git/ paths. Now watches specific git
internal files: index (stage/unstage), refs/ (commits/branches),
HEAD (checkout), MERGE_HEAD/REBASE_HEAD (merge/rebase).
External git operations (staging from another terminal, git hooks)
now trigger UI refresh automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4d68766Fix stale state after stage/unstage: synchronous refresh
After a git stage/unstage command, immediately re-fetch repo state
instead of relying on async send(self(), :refresh). This prevents
the next keypress from seeing stale file states where a just-staged
file still appears as unstaged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9f8b342Add plumbing_status/1 using diff-index, diff-files, ls-files
New Runner.plumbing_status/1 returns [FileState.t()] directly
from three plumbing commands instead of parsing porcelain output.
Runs alongside the existing status/1 for now.
9 integration tests covering all file states: untracked, staged_new,
unstaged_modified, staged_modified, partial_modified, staged_deleted,
unstaged_deleted, and mixed multi-file scenarios.
157 total tests, 0 failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dc37df7Fix s/u keybindings: work from both file_list and diff_view
stage_selected and unstage_selected were restricted to :file_list
focus only. Now they fire from :diff_view too (anywhere a file is
selected). Guards on nil selected_file prevent empty commands.
Log_view/log_detail still no-op (no file context there).
6 new Navigation tests for s/u across focus states.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
c6da445Add FileState state machine for git staging lifecycle
Every file is in exactly one state (untracked, staged_new,
unstaged_modified, staged_modified, partial_modified, etc).
Each state returns the correct git command for stage/unstage
via stage_command/1 and unstage_command/1.
DiffLive now derives FileState from the file entry and delegates
to Runner.exec_file_command/2 instead of ad-hoc conditionals.
All Runner errors are logged and surfaced via put_flash.
24 new FileState tests. 142 total, 0 failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8ed2fd4feat: replace tungstenite with hand-rolled WebSocket over raw TCP
tungstenite corrupts internal state when read/write are interleaved
from different threads, even with Mutex protection. Replace it with
a minimal WebSocket implementation (~150 lines) that:
- Does HTTP upgrade handshake byte-by-byte (avoids BufReader read-ahead)
- Uses TcpStream::try_clone() to split into independent read/write halves
- Reader thread owns the read half, writer Mutex holds the write half
- No shared internal framing state — reads and writes are independent
- SHA-1 for the accept key via sha1_smol (only external dep)
Also removes the sleep() debug logging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9a9b350fix: set read timeout on WS connections for Rust backend
Both server-accepted and client connections now have a 50ms read
timeout. This prevents the recv loop from holding the socket Mutex
indefinitely, allowing send operations to interleave. Without this,
the Rust server couldn't send frames because recv blocked the lock.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3a512b1fix: use dedicated threads for Rust ws operations
Temper's Rust runtime uses a single-threaded task runner. Blocking
ws operations (accept, send, recv) would block the runner and
prevent other async blocks (like readLine) from processing.
Spawn dedicated threads for ws_accept, ws_send, and ws_recv instead
of going through crate::run_async. This lets the WS operations
block independently while other async work continues.
Also adds error logging to ws_recv for debugging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
561c49afeat: add Python backend for std/ws and std/io terminal size
Wire WebSocket support (wsListen, wsAccept, wsConnect, wsSend, wsRecv,
wsClose) and terminal size detection (terminalColumns, terminalRows)
for the Python backend.
Uses hand-rolled WebSocket over raw TCP (same approach as Rust) with
socket.dup() for read/write split and a dedicated reader thread per
connection. No external dependencies — just stdlib socket, hashlib,
base64, struct, threading.
Terminal size uses shutil.get_terminal_size().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
936910dfix: guard wsSend against closed WebSocket connections
Check readyState before calling send(), and wrap in try/catch to
prevent synchronous throws from crashing the server when a client
disconnects mid-broadcast.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
73b848dfix: Rust ws support - rename functions, fix visibility and stream types
- Rename support functions to std_ws_* to avoid colliding with
Temper-generated panic stubs (same pattern as std_sleep/std_read_line)
- Change visibility from pub(crate) to pub for cross-crate access
- Accept &dyn WsServerTrait/WsConnectionTrait instead of &WsServer to
work with the Rust backend's interface deref codegen
- Use WsStream enum to handle both WebSocket<TcpStream> (server-accepted)
and WebSocket<MaybeTlsStream<TcpStream>> (client-connected)
- Use temper_core::cast() for downcasting instead of manual as_any chain
- Add .into() for tungstenite 0.26 Message::Text(Utf8Bytes) change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
e76ab32fix: add ws npm dependency to temper-core package.json
The ws.js support file dynamically imports the 'ws' package, but it
wasn't listed as a dependency. After temper build regenerated the
output, npm install wouldn't install ws, causing wsListen/wsConnect
to fail silently at runtime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7718c2ffix: spawn OS thread for Rust nextKeypress to avoid blocking async runner
The Rust backend uses a SingleThreadAsyncRunner, so the blocking
crossterm::event::read() call was starving the game tick coroutine.
Spawn a real OS thread for the blocking read and complete the promise
from there, allowing other async tasks to proceed concurrently.
Signed-off-by: Robert Grayson <bobbbygrayson+github@gmail.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Robert Grayson <bobbbygrayson+github@gmail.com>
03f6b99feat: add std/ws WebSocket module and std/io terminal size detection
Rebased onto do-crimes-to-play-snake: resolved conflicts with std/keyboard,
updated Rust terminal size to use crossterm instead of libc, added ws to
stdSupportNeeders set.
d9f3020fix: channel-based I/O for Rust WebSocket connections
Replace Mutex-shared WebSocket with a dedicated I/O thread per
connection that communicates via mpsc channels. The I/O thread
owns the WebSocket exclusively and polls for both send and recv,
avoiding tungstenite's internal state corruption from concurrent
access. Send is now non-blocking (channel push), recv blocks on
the channel receiver in a spawned thread.
This fixes the ResetWithoutClosingHandshake error that occurred
when the server's recv loop and send loop competed for the socket.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7fceb70test: update snapshots for io.js and keyboard.js in CLI tests
JsRunFileLayoutTest: add io.js and keyboard.js to expected file trees
in both std/ and temper-core/ directories.
ReplTest: allow scheduler call in Lua translation output.
Signed-off-by: Robert Grayson <bobbbygrayson+github@gmail.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Robert Grayson <bobbbygrayson+github@gmail.com>
9bb8efachore: remove debug logging from Rust ws support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
769ab8ffix: make all 6 backends run the snake game correctly
Lua:
- Replace temper.TODO stub with cooperative coroutine scheduler
- async {} now compiles to temper.async_launch() (was "TODO")
- LuaTranslator emits temper.run_scheduler() after top-level code
- Non-blocking IO: sleep uses deadline-based promises, readLine uses
stty min 0 time 0 for polling
- Round-robin scheduler drives multiple async blocks cooperatively
Rust:
- Fix missing temper-std dependency in generated Cargo.toml
- Connected functions (stdSleep, stdReadLine) reference temper_std::
paths but bypassed the import-based dependency scan
- RustTranslator now tracks usedSupportFunctionPaths
- RustBackend scans these after translation to inject temper-std dep
with correct features
- Also fixes missing temper_std::init() in generated lib.rs
- Add raw terminal mode for single-keypress input
Java:
- Fix waitUntilTasksComplete() 10-second hard timeout
- Now loops until ForkJoinPool is truly quiescent
- Add raw terminal mode via stty for single-keypress input
C#:
- Update target framework from net6.0 to net8.0 (current LTS)
- Namespace-qualify OrderedDictionary and AsReadOnly in RegexSupport.cs
to avoid conflicts with System.Collections.Generic.OrderedDictionary
introduced in .NET 9+
- Add single-keypress input via Console.ReadKey
Python:
- Add raw terminal mode for single-keypress input via tty/termios
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4d44c8fbe-lua: debug shift operator wrappers
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
a3fa212specialize lua bitops so we can mask shift distances
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
70b29eaTokenSourceAdapter works with margin characters
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
09dfee2remove debug cruft
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
53e5d99fix test goldens
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
91cebe8astbuild: Reworked expression inversion for MQ strings
Previously, TokenSourceAdapter assumed brackets, (`{:` ... `:}`)
around statement fragments.
Now we have margin characters that, in a left to right scan, give
enough context.
This reworks *TokenSourceAdapter* to cue off the margin characters
when inverting statements in complex string expressions, and to
differentiate between `~` and `"` margin characters when stripping
incidental whitespace.
It also updates documentation around string expression syntax,
semantics, and whitespace elision.
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
6d83930Define missing shift and bitwise operators (#375)
Previously, Temper was missing some bit-twiddling operators:
- Shift operators: `<<`, `>>`, and `>>>`.
- Bitwise negation: `~`
- Exclusive or: `^`
In keeping with JavaScript for syntactic choices, this commit defines
these operators with that punctuation.
Unlike JavaScript, our semantics are the same as C-like languages that
do not have undefined behaviour on over/under flow, e.g. Java and C#.
This commit also defines a functional test that exercises these
operators.
Some bugs were flushed out as a result:
- C#'s *System.Text.Convert.ToString* when used to format numbers as
non-decimal, formats as if cast to unsigned: no `-` sign for negative
numbers.
- be-js does not always render *Int64*s as bigints.
- Lua's number formatting ran into trouble with *math.mininteger*
because of the 2's complement fixed point around negation with the
smallest representible integer.
## Caveat
~~Lua, Rust, and C++ do not currently pass.
This commit will remain as part of a draft PR until they do.~~
4d4c3d0fix: simplify readLine to actual line reading across all 6 backends
Address PR review feedback: readLine was doing raw TTY single-keypress
reading instead of actual line reading. This simplifies all backends to
do buffered line input, removes all process-exit calls (System.exit,
process.exit, os.kill, Environment.Exit), fixes the Java
completeExceptionally bug, and reverts the incidental net8.0 bump in
the C# csproj back to net6.0.
The keyboard/keypress functionality will be provided by a separate
std/keyboard module.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Robert Grayson <bobbbygrayson+github@gmail.com>
eafa620ran code formatter
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
6d734c4be-lua: number formatting with base
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
92fd175lexer: Lex new multi-line string context.
This commit enables the new margin character based syntax.
It removes support for `{:...:}` lines, and recognizes `:` and `~` as
margin characters where the former allows for open tokens like the others.
This commit adds TokenType info when managing the delimiter stack so that
we can make clear distinctions between `"` as a left delimiter, a right
delimiter, or a margin character.
It also reworks regex literal syntax to use left and right delimiters to
avoid ambiguity in later passes with `/foo/i` as a quoted string segment
on a margin line.
This commit also simplifies TokenClusterTest to, instead of emulating the
delimiter stack and saved element stack, to instead interrogate the Lexer
using internal `*ForDebug` methods. The nesting levels of synthesized
tokens look less pretty, but it matches what updateTokenClusters and
popDelimiterStack actually do.
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
4bc8cccAvoid parse fail on old luas
Signed-off-by: Tom <tom@contextfree.info>
97a2086be-cpp: unsigned right shifting support
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
6707857fix test golden from adding int.js as a supporting file
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
4519f5dlexer: ensure we do not reset the content line hint inside an interpolation
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
75ad3c0parse: fix OPP test cases for new regex/mq-string syntax and lexing conventions
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
9c68c81release-notes: breaking change warning re string syntax.
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
70c75bbtest: add functional test for `std/io` `sleep()` across backends
Adds `control-flow/io-sleep/io-sleep.temper.md` to the functional test
suite, verifying that `sleep()` from `std/io` works correctly:
- Sleep returns and execution continues after `await`
- Multiple sequential sleeps work
- Zero-ms sleep resolves immediately
- Sleep interleaved with computation produces correct results
Uses short delays (5-10ms) to avoid slowing the test suite.
Passes on: JS, Python, Lua, Java 17, C#.
Skipped on Rust (`@Ignore`) because the Rust functional test
infrastructure only links `temper-core`, not `temper-std`, so
`import("std/io")` produces an unresolved crate at cargo build time.
The Rust `sleep` implementation itself works (verified manually via
the snake game with `cargo run`).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1650f8bDefine missing shift and bitwise operators
Previously, Temper was missing some bit-twiddling operators:
- Shift operators: `<<`, `>>`, and `>>>`.
- Bitwise negation: `~`
- Exclusive or: `^`
In keeping with JavaScript for syntactic choices, this commit
defines these operators with that punctuation.
Unlike JavaScript, our semantics are the same as C-like languages that
do not have undefined behaviour on over/under flow, e.g. Java and C#.
This commit also defines a functional test that exercises these
operators.
Some bugs were flushed out as a result:
- C#'s *System.Text.Convert.ToString* when used to format numbers as
non-decimal, formats as if cast to unsigned: no `-` sign for
negative numbers.
- be-js does not always render *Int64*s as bigints.
- Lua's number formatting ran into trouble with *math.mininteger*
because of the 2's complement fixed point around negation with the
smallest representible integer.
## Caveat
Lua, Rust, and C++ do not currently pass.
This commit will remain as part of a draft PR until they do.
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
085161fchore: add stdReadLine and stdSleep to connected list in README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Robert Grayson <bobbbygrayson+github@gmail.com>
ac4ef18feat: add std/io module with sleep() and readLine() across 6 backends
This commit adds two new @connected primitives to Temper's standard
library — sleep(ms) and readLine() — wired across all six compilation
backends: JavaScript, Python, Lua, Rust, Java, and C#.
These are the first I/O primitives in Temper that enable interactive,
real-time programs to be written entirely in the Temper language without
any host-language wrapper scripts.
The motivation: a snake game written in pure Temper needed a game loop
that ticks every 200ms. Previously, Temper had no way to pause execution
or read user input. The only I/O primitive was console.log(). Programs
could compute and print, but could not wait or listen. This meant any
interactive program required a host-language wrapper (a Node.js script,
a Python script, etc.) to drive the game loop with setTimeout or
time.sleep, calling into the compiled Temper module from outside.
With sleep() and readLine(), a Temper program can now do this:
let {sleep} = import("std/io");
async { (): GeneratorResult<Empty> extends GeneratorFn =>
do {
var game = newGame(20, 10, 42);
while (game.status is Playing) {
game = tick(game);
console.log(render(game));
await sleep(200);
}
} orelse void;
}
That code compiles and runs identically on JS, Lua, Rust, Java, and
Python (via temper run). No wrapper. No FFI. One source, six targets.
--- THE ARCHITECTURE ---
Temper's @connected decorator system is the bridge between portable
Temper code and backend-specific native implementations. A connected
function has a Temper declaration with a panic() body that is never
executed — the compiler intercepts the call and routes it to a native
implementation registered in the backend's SupportNetwork.
The wiring for each connected function follows a 4-layer pattern:
1. Temper declaration: @connected("key") in a .temper.md file
2. Kotlin SupportNetwork: registers the key in the backend compiler
3. Runtime implementation: actual native code (.js, .py, .lua, .rs, etc.)
4. Resource registration: tells the build system to bundle the file
This commit touches all four layers for all six backends.
--- THE TEMPER DECLARATION (frontend) ---
A new std/io module is created at:
frontend/.../std/io/io.temper.md
It declares two functions:
@connected("stdSleep")
export let sleep(ms: Int): Promise<Empty> { panic() }
@connected("stdReadLine")
export let readLine(): Promise<String?> { panic() }
Key design decisions:
- sleep() returns Promise<Empty>, not Promise<Void>. This is because
Temper's await builtin requires the promise's type parameter to extend
AnyValue, and Void does not. Empty is a singleton class that does
extend AnyValue, so Promise<Empty> is the correct return type for a
"returns nothing meaningful" async operation.
- readLine() returns Promise<String?>, nullable because EOF returns null.
- The bodies are panic() — a convention matching stdNetSend in std/net.
The @connected decorator ensures the body is never reached; the
backend substitutes its own implementation at compile time.
The std config (std/config.temper.md) gains import("./io") to include
the new module in the standard library.
--- JAVASCRIPT BACKEND (be-js) ---
Files changed:
- be-js/.../temper-core/io.js (NEW)
- be-js/.../temper-core/index.js (export added)
- be-js/.../JsBackend.kt (resource registered)
- be-js/.../JsSupportNetwork.kt (keys added to supportedAutoConnecteds)
The JS backend uses the "auto-connected" pattern: connected keys listed
in the supportedAutoConnecteds set are automatically mapped to exported
functions whose names follow the connectedKeyToExportedName convention.
"stdSleep" maps to an exported function named stdSleep in io.js.
The implementation:
export function stdSleep(ms) {
return new Promise(resolve => setTimeout(() => resolve(empty()), ms));
}
This returns a native JS Promise that resolves after ms milliseconds
via setTimeout. It resolves with empty() (the Temper Empty singleton)
to match the Promise<Empty> return type.
readLine() returns a Promise that reads from process.stdin via the
'data' event, or resolves with null if stdin is unavailable (browser).
--- PYTHON BACKEND (be-py) ---
Files changed:
- be-py/.../temper_core/__init__.py (functions added)
- be-py/.../PySupportNetwork.kt (PySeparateCode + pyConnections)
Python's async model uses concurrent.futures.Future with a
ThreadPoolExecutor. The existing _executor and new_unbound_promise()
infrastructure (already used by stdNetSend) is reused:
def std_sleep(ms):
f = new_unbound_promise()
def _do_sleep():
time.sleep(ms / 1000.0)
f.set_result(None)
_executor.submit(_do_sleep)
return f
The sleep happens on a worker thread; the Future resolves when done.
The main thread's generator-based coroutine system picks up the
resolution via the existing _step_async_coro machinery.
Python programs run via `temper run --library snake -b py`, which
generates an entry point that calls await_safe_to_exit() to keep the
process alive until all async tasks complete.
--- LUA BACKEND (be-lua) ---
Files changed:
- be-lua/.../temper-core/init.lua (functions + async stub added)
Lua is the most interesting case. It has no Promises, no event loop,
and no async/await. The Lua translator compiles:
- async { ... } → temper.TODO(generatorFactory)
- await expr → expr:await()
Previously, temper.TODO was undefined (hitting the __index metamethod
fallback which errors with "bad connected key: TODO"). This commit adds
a minimal stub:
function temper.TODO(generatorFactory)
local gen = generatorFactory()
local co = gen()
end
This creates the generator and steps it once via coroutine.wrap(),
which runs the entire body synchronously (since all awaited operations
complete immediately in Lua).
For sleep and readLine, the functions are synchronous and return a table
with an :await() method so the compiled await translation works:
local function make_resolved(value)
return { await = function(self) return value end }
end
function temper.stdsleep(ms)
local sec = ms / 1000
local ok, socket = pcall(require, "socket")
if ok then
socket.sleep(sec)
else
os.execute("sleep " .. string.format("%.3f", sec))
end
return make_resolved(nil)
end
The sleep implementation tries LuaSocket first (sub-second precision),
falling back to os.execute("sleep ...") on systems without it.
The naming convention: the default else clause in LuaSupportNetwork's
translateConnectedReference converts "stdSleep" to "stdsleep" via
.replace("::", "_").lowercase(), which matches temper.stdsleep().
No Kotlin changes needed for Lua.
--- RUST BACKEND (be-rust) ---
Files changed:
- be-rust/.../std/io/support.rs (NEW)
- be-rust/.../RustBackend.kt (feature + stdSupportNeeders)
- be-rust/.../RustSupportNetwork.kt (FunctionCall entries)
Rust uses a custom async runtime (not tokio) based on Promise<T>,
PromiseBuilder<T>, and SafeGenerator<T>. The pattern matches stdNetSend
exactly: create a PromiseBuilder, spawn async work via run_async(),
complete the promise from the worker.
pub fn std_sleep(ms: i32) -> Promise<()> {
let pb = PromiseBuilder::new();
let promise = pb.promise();
crate::run_async(Arc::new(move || {
let pb = pb.clone();
SafeGenerator::from_fn(Arc::new(move |_| {
std::thread::sleep(Duration::from_millis(ms as u64));
pb.complete(());
None
}))
}));
promise
}
The connected reference uses full crate paths ("temper_std::io::std_sleep")
because the function lives in the std crate but is called from user crates.
The "io" feature is added to stdSupportNeeders and the generated
Cargo.toml as io = [] (no external dependencies — only std library).
--- JAVA BACKEND (be-java) ---
Files changed:
- be-java/.../temper/core/Core.java (methods added)
- be-java/.../JavaSupportNetwork.kt (separateCode + connections)
- be-java/.../StandardNames.kt (qualified names)
Java maps Temper Promises to CompletableFuture<T>. The stdSleep
implementation runs Thread.sleep on the ForkJoinPool:
public static CompletableFuture<Optional<? super Object>> stdSleep(int ms) {
CompletableFuture<Optional<? super Object>> future = new CompletableFuture<>();
ForkJoinPool.commonPool().execute(() -> {
Thread.sleep(ms);
future.complete(Optional.empty());
});
return future;
}
The return type is CompletableFuture<Optional<? super Object>> because
Temper's Empty type maps to Tuple<object?> (via the connectedTypes map),
and the generated Java code declares the variable as such.
--- C# BACKEND (be-csharp) ---
Files changed:
- be-csharp/.../std/Io/IoSupport.cs (NEW)
- be-csharp/.../CSharpBackend.kt (resource registered)
- be-csharp/.../CSharpSupportNetwork.kt (StaticCall entries)
- be-csharp/.../StandardNames.kt (namespace + member names)
C# has native async/await with Task<T>, making this the most natural
fit. The implementation uses Task.Delay for non-blocking sleep:
public static async Task<Tuple<object?>> StdSleep(int ms)
{
await Task.Delay(ms);
return Tuple.Create<object?>(null);
}
The return type is Task<Tuple<object?>> because C# maps Temper's Empty
to System.Tuple (via the connectedTypes map entry "Empty" -> systemTuple).
--- VERIFICATION ---
All backends compile. Tested with a snake game (18 unit tests passing
on JS backend, game loop running on JS, Lua, Rust, Java, and Python):
JS: node temper.out/js/snake/index.js
Lua: cd temper.out/lua && lua snake/init.lua
Rust: cd temper.out/rust/snake && cargo run
Java: javac + java -cp build snake.SnakeMain
Python: temper run --library snake -b py
C#: dotnet build succeeds (needs net6.0 runtime to execute)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8b168f6be-lua: implement intold shifts based on div/mult by pow(2)
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
86d4ee7regenerated docs and fixed a test
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
6ed791dAvoid parse fail on old luas (#381)
- This doesn't actually fix the int shifty funtest on old luas, but from
manual testing, I *can* run the int limits funtest without crashing on
both luajit and lua 5.2 (but not 5.1, but maybe that was an existing
issue?) with these changes.
- Before these changes, old luas don't run at all for any funtest
- Here's the error on plain 5.1:
```lua
C:\Users\tjpal\Apps\lua-5.1.5_Win64_bin\lua5.1.exe: .\temper-core/intold.lua:38: attempt to call field 'trunc' (a nil value)
stack traceback:
.\temper-core/intold.lua:38: in function 'rshift'
.\temper-core/intold.lua:64: in function 'int32_mul_parts'
.\temper-core/intold.lua:199: in function 'int32_mul'
.\work\init.lua:10: in main chunk
[C]: ?
```
2062adebe-rust: use wrapping versions of shift on Rust to avoid panics on over/under-flow
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
133c620feat: add std/keyboard module with nextKeypress() across 6 backends
New std/keyboard module separate from std/io, providing single-keypress
input for interactive programs like the snake game.
API: nextKeypress(): Promise<String?> returns key names as strings
("a", "ArrowUp", "Enter", "Escape", etc.) or null on EOF.
Backend implementations:
- Java: stty raw mode (Unix) + PowerShell ReadKey (Windows)
- JavaScript: Node setRawMode with escape sequence parsing
- Python: tty/termios (Unix) + msvcrt (Windows)
- Lua: cooperative scheduler with poll_char and PROMISE_KEYPRESS state
- C#: Console.ReadKey with ConsoleKey enum mapping
- Rust: crossterm crate for cross-platform keypress reading
Windows support on 5/6 backends (Lua is Unix-only for now).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Robert Grayson <bobbbygrayson+github@gmail.com>
"Debut. Features Billy Strings on guitar and added vocals (and a nice jam, too)."
TAP TO SEE AND PLAY JAMS
What's Going Through Your Mind
.500
TAP TO FLIP BACK
8 jams
★
2024-08-07
10:29
Van Andel Arena · Grand Rapids, MI
Debut. Features Billy Strings on guitar and added vocals (and a nice jam, too).
★
2024-08-15
23:05
The Woodlands · Dover, DE
Nothing green here. With Trey back on lead vocals, a great version of the song breaks from what already seems like a signature lick to spark a huge jam. About seven minutes in, sound swells to inform that patterned bounce, and then the effects kick in, with Page laying heavy on his synths. Play coasts before, as one, the four become more active, propelling the play. Trey shears blocks of sound, an impossibly cool pattern, one that, on Fish's back beat, builds to a thrilling conclusion.
★
2024-08-31
16:34
Dick's Sporting Goods Park · Commerce City, CO
Question isn't when, but if "WGTYM?" won't jam. Seems improbable, given Trey's line from the vocal section is, like a drug, seemingly designed to bring the band back for more. Impressive, extended, synth-heavy improv runs to break for a very notable -> into "Crosseyed."
★
2024-12-31
17:14
Madison Square Garden · New York, NY
The centerpiece of the NYE '24 extravaganza with additional backing vocals, the jam takes a drastic turn into EDM, a genre heretofore unexplored on the Phish stage, and is interspersed with vocal quotes from several other Phish tunes (see setlist notes) before finally crashing -> into "CDT".
★
2025-02-01
?
Moon Palace · Quintana Roo, Cancun, Mexico
The first post-EDM version of "WGTYM" is a distinctly slower version of the song. However, the jamming out of the song remains top-notch. Fish takes the reins initially leading a driving, percussive jam. This later gives way to a feel-good explosion of jubilant play that evokes Phish on a beach. The band eventually winds its way back to the "Mind, mind, mind," refrain.
★
2025-06-24
42:40
Petersen Events Center · Pittsburgh, PA
-> from a fun "YEM". Given how strong its jamming was from the first few versions played, it may have seemed inevitable a HUGE version would appear. This 40 minute masterpiece is just that. Moving from section to section with deftness and poise. Clearly, the band feels comfortable jamming this song, and this may be the cream of the "WGTYM" crop. Must-hear.
★
2025-07-18
28:07
United Center · Chicago, IL
Though Trey hints early on that the answer to the question may be that there is "nobody home", there's actually plenty to discover in this wide ranging version that finds the band locking on to a handful of memorable progressions, moving seamlessly between sections, and soaring over peaks before accelerating through groove laden depths to deliver a sustained and fiercely rocking finale that eventually finds itself becoming suddenly "Crosseyed".
★
2025-09-14
17:34
Coca-Cola Amphitheater · Birmingham, AL
> from a AWOH. Patient, rhythmic, and hypnotic; the band takes an approach here that, while it feels familiar, is interpreted through the fresh lens of the past handful of years' inventive play and that occupies a space halfway between aggression and ambience. Fans of peak free exploration will want to seek out this version.
Disappointed that War does not lead to actual combined-arms conflict.
jeff
04:01 AM
that would be hard to conjure
jeff
04:02 AM
I am so excited that this works and is a successful combination of windows and old apple lol
Uechi Nerd
04:02 AM
Probably for the best, actually. That shit is very very messy.
Uechi Nerd
04:02 AM
I am intrigued and happy it works!
Uechi Nerd
04:03 AM
I respect the wizardry.
Visitor7804
04:05 AM
this is delightful.
jeff
04:06 AM
hell yeah visitor 7804, this is livin' brother
guy4get
04:07 AM
i've never felt so alive
EarlofVincent
04:09 AM
Commencing experiment in 3....2....
jeff
04:14 AM
1
leah
04:16 AM
hi!
leah
04:16 AM
this is lovely
jeff
04:21 AM
hi! lol I was just like what if I combined Mac and windows and added a flower tree of life and called it my homepage and then smoked some weed and made it happen in an empty mall in Connecticut
B. Droptables
10:51 AM
Always cool to play with your toys.
Visitor1128
08:47 AM
yo!
Visitor1128
08:48 AM
i can barely work my phone. what am i doing here?
jeff
09:04 AM
the phone is not optimized yet but it "kind of works" I am sorry lol
jeff
09:04 AM
you have to pick a username, then it goes to the chat, then if you hit the bottom tabs it'll let you go to the app sections.
Bobdawg
04:43 AM
Hi everybody this is my blog I hope you enjoy it I did some more changes and anyone can write a post here now for me.
dinkleberg
01:45 AM
ALL HAIL TREE OF LIFE
jeff
08:55 PM
hi Hacker News
jeff
04:28 PM
hey there I am not really Jeff
Mal Function
05:34 PM
Hey! Please reveal... how exactly do I actually use losselot on my Mac? I've run the git clone commend in Terminal.app and seem successfully to have installed into a new <losselot> sub-folder in my home folder but now???