* Additional things ** flan dev blocks 4-5s before first eval when the program doesn't call agent/start "ready on sock" prints in [merged_setup] before [accept_loop] ever runs. [merged_serve] (lib/dev.ml:4417) waits up to 10s (await ~ms:10000, line 4427) for agent.sock to exist before starting accept_loop. The listen socket is already up, so Emacs connects fine, but nothing answers until that wait finishes/times out. So a program with no (agent/start ...) looks "ready" but actually stalls the first request for up to 10s. Maybe accept_loop should start immediately and the agent-socket wait should happen concurrently / only gate agent-specific requests? Update: happening a lot with sand.flan, not just once. sand.flan calls agent/start (sand.flan:125) but only after rl/init-window (line 122) returns, so if window setup is slow the 10s wait in merged_serve lapses before agent/start ever runs — and since accept_loop is gated behind that wait, this isn't just a noisy log line, it's a real Emacs stall every time. Reinforces: fix should be starting accept_loop unconditionally rather than tuning the timeout. Update: likely explains C-M-i (completion-at-point) and eldoc both breaking at once. Both read flan--defs (emacs/flan.el:1511), refreshed only on connect and after an accepted eval, always through `ignore-errors` (flan.el:592, 1743, 1841, 2333). If a refresh lands during the accept_loop stall it just silently fails, leaving flan--defs empty/stale with nothing retrying it — killing both features with no visible error. Same underlying fix (start accept_loop unconditionally) should resolve this too. Update: also probably explains "redefined game-draw via C-c C-c but raylib never updates" — deliver (lib/dev.ml:159, called at line 764) pushes the new module over the same agent connection. A stale/half-set-up connection from the accept_loop race could report "ok" on install without the game thread's agent/poll ever actually picking it up at the next frame boundary. Quick workaround: M-x flan-restart-program for a fresh connection, then redefine again. Real fix is still the accept_loop one. ** add // for forced truncating division? Lexically free (comments are `;`, not `/`). But nothing is actually missing: `/` on two ints already truncates toward zero (sdiv/udiv, lib/emit.ml:2529), and casting a float division truncates toward zero too ((i32 (/ 7.0 2.0)) -> fptosi -> 3, lib/emit.ml:2909). So `//` as sugar for that is trivial. Catch: Python's `//` is not toward-zero truncation, it's *floor* division — rounds toward -infinity. (-7 // 2) is -4 in Python, but sdiv/fptosi give -3. So "Python-like //" and "forced truncation" are two different asks. Resolved: Joe wants the toward-zero-truncation one, for when a float division needs an int result. (int (/ a b)) already does this — no new operator needed. Not adding //. ** "generic code over type variable X" is a bad error for a plain typo Hit with `int` before defining the defalias for it. resolve_name (lib/check.ml:776-778) treats any unrecognized lowercase name as a type variable by default; near_miss (lib/check.ml:670-698, single-edit-distance only) is what rescues real typos into "unknown type X — did you mean Y?" (line 770-772). `int` -> `i32` is 2 substitutions, outside that net, so it falls into "generic code over the type variable int is not implemented yet — milestone 5" instead of a plain "unknown type int". Common near-misses (int, float, double, str, ...) all get this confusing message rather than "unknown type". Maybe near_miss should also check a short hardcoded list of common non-flan spellings (int/float/double/bool/str etc. from other languages) regardless of edit distance. ** eval result ghost text position/minibuffer, both deliberate Both things I want changed are documented decisions, not bugs: - Position is "end of the form sent" (flan.el:1822-1825), not end of line — deliberately different from flan-watch's end-of-line ghost text so the two don't get confused (flan.el:1381-1389). - Overlay and minibuffer echo are mutually exclusive on purpose (flan-inline-result docstring, flan.el:91-101): "saying the same number twice is how a reader learns to stop reading both." I want it at end of line + always also in the minibuffer. Revisit — would mean changing flan--report's `at` computation and the shown/echo either-or in flan.el:1847-1848. ** "unknown name u8" from (defconst grid [rows [cols u8]]) — not a real i8/u8 bug i8/u8 etc. all exist fine (Types.ikind_of_name, lib/types.ml:72). Real issue: defconst's 2-arg form (defconst name value) has no type slot — the second form is always parsed as a VALUE via expr, never as a type (lib/parse.ml: 1341-1345). So [rows [cols u8]] became an array *literal*, and `u8` inside it read as a bare variable reference, which is genuinely unbound as a value -> "unknown name u8". defconst has no "type only, no value" form; that's defvar's job. Fix: use defvar instead — (defvar grid [rows [cols u8]]) is defvar's 2-arg (name Type) form (lib/parse.ml:1333-1335), zero-initialized, and parses the bracket correctly as a type via texpr. Fits anyway since grid is mutable. ** (when test) with no body should work like (when test (do)) lib/parse.ml:305-309: `when` is a hardcoded special form (not a macro yet — "until macros land at milestone 5") and requires `body <> []`, else fails "when is (when test body ...)". No real reason for the restriction — empty body could just become Ast.Do [], same as (do) already means. One-line fix (drop the `body <> []` guard) whenever this file gets touched. ** bare struct literal {.field v} can't infer its type from context (e.g. defn return type) `(defn get-mouse-cell [] Cell ... {.row r .col c})` fails to parse — "a bare map is not an expression; write (Type {.field v})". Root cause: this refusal is in `expr` at PARSE time (lib/parse.ml:274-276), before type-checking ever runs, so it can't see that the enclosing defn's return type is Cell. check.ml does support expected-type-driven checking elsewhere (`check ~want:ty`), but a bare struct literal never reaches it — parse rejects it first. To make this work, the refusal would need to move from parse into check, so it can consult a `want` type (return type, let annotation, etc.) and only fail when there truly isn't one available. Real feature, not a small change. ** struct destructuring shorthand: {.row .col} binding same-named locals Only two struct-pattern forms exist: {:keys [x y]} (lib/parse.ml:729-753) and {name .field} pairs (line 759-762) — both spell the field name twice ({:keys [x y]}) or need name+dot per field. :keys makes sense for dyn maps (keys aren't statically known field names), but a struct's fields are typed and known, so a bare {.row .col} that just binds `row`/`col` locals directly would read better and is the common case. Would be a new match arm in `dmap` (lib/parse.ml:724-778) for a lone .field symbol with no name before it. No obvious grammar collision — nothing currently matches a bare .field symbol on its own. Not implemented. ** C-c C-i should auto-inspect at point, C-u C-c C-i for the minibuffer Feature request. Today flan-inspect (emacs/flan-inspect.el:681-696) always prompts via read-string, pre-filled with the sexp before point — no current-prefix-arg handling anywhere in the file, and emacs/MANUAL.md:369 documents no C-u variant either. Not sure if I'm misremembering discussing this for flan-inspect specifically or some other command — check that too if it comes up again. Wanted: plain C-c C-i inspects the expression at point with no prompt; C-u C-c C-i opens the minibuffer to type a different one. ** inc/dec (pure) and ++/-- (mutating) — doable as user macros, no compiler change No inc/dec/1+/++/-- exist anywhere (prelude or special forms) — (+ x 1) is the only spelling today. Unlike most items above, this needs no compiler change: defmacro already exists at user level (vendor/raylib's with-drawing etc.), and `place` (what `set` parses — lib/parse.ml:961-983: vars, .field, at, deref) is already general enough. Can write today: (defmacro inc [args] `(+ ~(at args 0) 1)) (defmacro dec [args] `(- ~(at args 0) 1)) (defmacro ++ [args] `(set ~(at args 0) (+ ~(at args 0) 1))) (defmacro -- [args] `(set ~(at args 0) (- ~(at args 0) 1))) inc/dec are generic for free since +/- already are. ++/-- reuse the place twice (read then write) — fine for a plain var, double-evaluates a place like (at arr (compute-index)) if the index has side effects. Same non-hygienic-macro tradeoff with-drawing/with-mode-2d already accept. Maybe worth putting these in the prelude itself rather than per-project. ** (Cell 1 2) as a positional struct constructor Designated initializers (C/Odin-style) already exist: {.field v ...} + ZII zero-fill for omitted fields (zii_fill, lib/check.ml:3814), any order, {} fully zeroed. Not missing — just spelled with braces/dots. Positional (Cell 1 2) genuinely doesn't exist, and can't be added as a parser special case the way {.field ...} was: struct-literal recognition happens at PARSE time purely by syntax shape (Sym name applied to a {.field...}-shaped map, lib/parse.ml:593-600) — the parser has no symbol table, so (Cell 1 2) is syntactically identical to any function call (parse.ml:602-603 Ast.Call). Would need check.ml to notice an Ast.Call's head resolves to a struct type rather than a function, and reinterpret the positional args in field-declaration order there instead. Same parse/check boundary issue as the bare-struct-literal-return-type item above — recurring pattern worth keeping in mind for future struct/type ergonomics asks. ** a DEADBEEF-style sentinel-fill builtin, instead of zero Idea: a debug memset that writes a real byte pattern instead of the zero llvm.memset already uses (lib/emit.ml:934), for catching reads of uninitialized memory. Distinct from Tast.Uninit, which compiles to LLVM `poison` (lib/emit.ml:1613) — a semantic "don't care" marker, not an actual inspectable byte pattern. Technical snag: llvm.memset only takes a single repeated i8, so a real 4-byte 0xDEADBEEF pattern needs a fill loop, not memset — more expensive. This is why MSVC/glibc-style debug allocators use a single repeated byte instead (0xCC/0xCD/0xDD/0xFD). Need to decide: cheap single-byte sentinel, or a real 4-byte pattern via a loop. Name candidates: - single-byte (memset-cheap): 0xCC (MSVC uninit stack), 0xCD (MSVC "Clean" unwritten heap), 0xDD (MSVC "Dead" freed heap), 0xFD (MSVC fence/guard) - 4-byte (loop, reads great in a hex dump): 0xDEADBEEF, 0xBAADF00D (Windows LocalAlloc uninit), 0xFEEEFEEE (Windows HeapFree'd), 0xDEADC0DE, 0xC0FFEE, 0x8BADF00D (Apple watchdog-timeout crash code) Built, 2026-09-20, as two builtins rather than one — the author's answer to the cheap-byte-or-real-pattern question was "why not both? we need some sort of memset -1 right? and dead-beef can loop, that's fine". They are (filled BYTE) and (dead-beef), spelled the way [zeroed] is: the value of whatever type is expected of them, so (set grid (filled 0xFF)) is how a place is filled and there is no place-taking form to learn beside [set]. (dead-beef) writes the default DEADBEEF and (dead-beef 0xBAADF00D) writes the pattern given, under one byte-order rule: a pattern's ascending bytes are its big-endian bytes, which is how the hex literal reads left to right. So every candidate listed above is spellable without the compiler naming any of them, and the bare form is checked into the spelled-out default rather than being a case a backend knows about. The pattern may be computed, not only written — same rule as the byte arm, and both backends byte-reverse at run time when it is. The snag above is why there are two and not one with a wider operand. The byte fill is one llvm.memset / one rep stosb; the 4-byte pattern is a loop on both sides, a counted dword loop in emit.ml and rep stosd in x86.ml, because the intrinsic really does only take a repeated i8. What may be filled is numbers, and structs and fixed arrays built out of numbers — nothing that carries a tag, a length, an owning pointer or a collector descriptor. That boundary, and why each refusal is the runtime's rather than a matter of taste, is written up in FIX.org, "The two byte fills". ** println output goes to *flan-output*, not inline in the repl Deliberate per emacs/flan-repl.el:40-44: "a value and the program's output are different things and arrive by different routes... showing them in one place would be convenient and wrong." Same split in flan-mode's C-x C-e. Not sure I like it — printed output feels disconnected from the eval that produced it when it's in a separate buffer/window. Revisit. ** main cannot actually be redefined Confirmed. emit_main (lib/emit.ml:3858-3861) calls the flan-level `main` via `fname "main"` — a direct symbol call. Every other call site goes through `body_of` (lib/emit.ml:1980-1994) instead, which loads from the dev-build indirection cell so a C-c C-c redefinition takes effect. emit_main skips that, so flan_program_main (what M-x flan-rerun re-enters) always runs the body main had at the initial build, no matter what's redefined into main's cell afterward. Not documented as a known limitation anywhere. Should probably route through body_of too, or say plainly that main is special. ** the "queued; the program is parked..." note is annoying lib/dev.ml:778-790. Fires on every C-c C-c redefine while parked — and a finished program is always parked, so re-evaling main after each run always shows this long note. Message is correct, just too verbose/frequent for the common case. Maybe shorten, or only show once per park / make it toggleable? ** (rl/with-drawing ()) fails with a confusing error `()` is never a valid expression (lib/parse.ml:278). with-drawing splices its arg verbatim (vendor/raylib/modes.flan:79-84), so `(rl/with-drawing ())` expands to `(do (begin-drawing) () (end-drawing))` and the bare `()` blows up deep in the expansion. Workaround: use `(rl/with-drawing (do))` — `(do)` with zero forms is a valid no-op (used elsewhere, e.g. examples/core-input-gamepad.flan:268). The macro does have an empty-body guard ((< (len args) 1) -> with-drawing-takes-a-body) but it only catches zero arguments ((rl/with-drawing)), not one argument that is itself (). Guard should probably also treat a single bare-() arg as "no body". `()` is reserved as the type-position spelling of Unit (Types.to_string Unit = "()", lib/types.ml:125) and has no value-position meaning today — the unit value only comes from `(do)`, a form with no body, etc. Position already disambiguates Vec/Map literals from their type spellings (parse.ml comments near line 266-273); worth checking whether () could get the same treatment — type in type position, unit value in expression position — instead of being refused outright everywhere.