Every tool has edges. This page shows where Melker's are, what degrades gracefully, what breaks, and how to diagnose it.
Sections 1-2 cover when not to use Melker. Section 3-4 cover terminal compatibility. Sections 5-8 are debugging guides. Section 9 is a real-world case study.
Cases where another tool is the better choice.
| Scenario | Why Melker is wrong | Use instead |
|---|---|---|
| Pixel-perfect design with brand guidelines | Terminal cells are coarse. Colors are theme-dependent. | Web app |
| Long-running daemon or background service | Melker owns the terminal. No detach/re-attach. | systemd + plain CLI |
| Heavy form input (file upload, drag-drop) | No native file picker, no drag-drop. | Web app or native GUI |
| Team has no terminal culture | Onboarding cost is real. Don't force it. | Whatever they use now |
| App needs to run on Windows cmd.exe | Relies on ANSI escape codes and Unix TTY. | PowerShell or web app |
| Sub-millisecond rendering (games, video) | 30 fps ceiling, character-grid resolution. | Native GPU app |
| Go team, Elm architecture preferred | Melker is TypeScript (Deno or Node.js). | Bubble Tea |
| Python team, CSS styling wanted | Different ecosystem. | Textual |
| Rust, no_std, or embedded target | JS runtime too heavy. | Ratatui |
| Need React ecosystem and component library | Melker has its own component model, not React. | Ink |
| Must ship a single static binary | No deno compile support. Node.js requires runtime install. | Bubble Tea, Ratatui |
| Need a mature ecosystem | Melker is new. Smaller community, fewer examples. | Bubble Tea, Ink, Textual |
If your use case isn't in this list, Melker is probably fine. The rest of this page is about what happens at the edges.
For a detailed feature-by-feature comparison with other TUI frameworks, see the TUI Comparison.
Feature gaps visible in the ecosystem. Stated plainly.
| Missing feature | Impact | Workaround |
|---|---|---|
| Grid layout | Flexbox only. No CSS Grid. | Nested row/column containers |
| Collapsible sections | No built-in accordion/collapsible. | Toggle visibility with display: none |
| Syntax highlighting | Code blocks in markdown are plain text. | Use a <canvas> with custom rendering |
| Single binary dist | Can't deno compile yet. | Distribute as .melker file, user installs Deno or Node.js |
| RTL text | No right-to-left text layout support. | Manual text direction in app code |
| Dependency graph | No fine-grained reactivity (Solid signals, React hooks). | createState() + bind for most cases. Explicit setValue() for the rest. |
These are design choices, not bugs. Melker trades fine-grained reactivity for a simpler model
(createState + bind handles most cases without a dependency graph),
and trades grid layout for a smaller layout engine. If any of these are dealbreakers, the
table in Section 1 points to frameworks that have them.
What works where. Graphics modes, color depth, and resolution.
| Terminal | sextant | quadrant | halfblock | sixel | kitty | iterm2 | Notes |
|---|---|---|---|---|---|---|---|
| Ghostty | yes | yes | yes | no | yes | no | |
| Kitty | yes | yes | yes | no | yes | no | |
| iTerm2 | yes | yes | yes | yes | no | yes | |
| WezTerm | yes | yes | yes | yes | yes | yes | |
| Alacritty | yes | yes | yes | no | no | no | |
| foot | yes | yes | yes | yes | no | no | |
| xterm | yes | yes | yes | yes | no | no | |
| Konsole | yes | yes | yes | yes | yes | yes | Sixel right-edge quirk |
| Windows Terminal | yes | yes | yes | no | no | no | |
| VS Code terminal | yes | yes | yes | yes | no | no | |
| Rio | no | yes | yes | no | no | yes | Use MELKER_GFX_MODE=iterm2 |
| TERM=linux | no | no | yes | no | no | no | Console, no Unicode 13 |
| tmux/screen | yes | yes | yes | no | no | no | Protocols auto-disabled |
| SSH | yes | yes | yes | no | no | no | Protocols auto-disabled |
| Terminal | Truecolor | 256-color | 16-color | Detection method |
|---|---|---|---|---|
| Most modern | yes | yes | yes | COLORTERM=truecolor |
| TERM=linux | no | no | yes | TERM value |
| Claude sandbox | no | no | grayscale | IS_SANDBOX=yes |
| Mode | Pixels/cell | Resolution (80x24) | Unicode | Font support |
|---|---|---|---|---|
| sextant | 2x3 | 160x72 | 13.0 | Most modern mono |
| quadrant | 2x2 | 160x48 | 1.0 | Near-universal |
| halfblock | 1x2 | 80x48 | 1.0 | Near-universal |
| block | 1x1 | 80x24 | N/A | Universal |
| pattern | 2x3 | 160x72 | N/A | Universal (ASCII) |
| sixel | native | native | N/A | Terminal-specific |
| kitty | native | native | N/A | Kitty, Ghostty |
| iterm2 | native | native | N/A | iTerm2, WezTerm |
Same app rendered under different graphics modes and color depths. The image degrades. The text stays sharp.
┌────────────────────────────┐
🬞🬵🬚🬋🬋🬋🬩🬋🬋🬩🬱🬭🬏 │ │
🬞🬻🬹🬭🬭🬭🬭🬱🬹🬭🬭🬭🬭🬱🬹🬏 │ System Status │
🬻🬻🬦🬦🬹🬹🬏🬏🬭🬞🬵🬹🬓🬓▌🬴 │ CPU: 42% Mem: 3.1 GB │
🬬🬨▐▐🬊🬕🬬🬱🬵🬍🬆🬂▌▌🬕🬕 │ Uptime: 14d 6h │
🬹🬹🬹▐🬹▌🬍🬬🬝🬄▐🬹▌🬓🬹🬹 │ Load: 1.2 0.8 0.5 │
🬨🬊▐▐🬻▌🬆🬂🬂🬊▐🬵▌▌🬄🬎 │ │
🬊🬬🬂🬁🬂🬀🬂🬂🬂🬂🬁🬂🬀🬂🬆🬆 │ │
🬁🬊🬎🬎🬊🬂🬎🬎🬎🬊🬆🬎🬎🬂 │ │
│ │
│ │
└────────────────────────────┘
┌────────────────────────────┐
▗▄▟▀▀▛▀▛▜▀▀▙▄▖ │ │
▗▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ │ System Status │
▀▀▀▐▀▀▄▖▜▗▟▀▌▌▀▀ │ CPU: 42% Mem: 3.1 GB │
▄▄▄▐▄▌▜▙▟▌▘▄▌▄▄▄ │ Uptime: 14d 6h │
▀▀▀▐▀▌▀▀▀▘▐▀▌▀▀▀ │ Load: 1.2 0.8 0.5 │
▄▄▄▐▄▌▄▄▄▄▐▄▌▙▄▄ │ │
▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │ │
▝▀▀▀▀▀▀▀▀▀▀▀▀▘ │ │
│ │
│ │
└────────────────────────────┘
┌────────────────────────────┐
▄▄▀▀▀▀▀▀▀▀▀▄▄ │ │
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │ System Status │
▀██▀▀▀▀▀▀▀▀▀▀▀▀▀ │ CPU: 42% Mem: 3.1 GB │
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │ Uptime: 14d 6h │
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │ Load: 1.2 0.8 0.5 │
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │ │
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │ │
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │ │
│ │
│ │
└────────────────────────────┘
System Status
CPU: 42% Mem: 3.1 GB
Uptime: 14d 6h
Load: 1.2 0.8 0.5
All four are the same app. Melker picks the best mode your terminal supports, or you
override with MELKER_GFX_MODE or the gfxMode prop. The image
degrades. The box-drawing and text stay sharp in all modes.
Same app, same sextant mode, three color depths. This is the constraint that catches people off guard: you think the limit is character-grid resolution, but it's actually color depth.
┌────────────────────────────┐
🬞🬵🬚🬋🬋🬋🬩🬋🬋🬩🬱🬭🬏 │ │
🬞🬻🬹🬭🬭🬭🬭🬱🬹🬭🬭🬭🬭🬱🬹🬏 │ System Status │
🬻🬻🬦🬦🬹🬹🬏🬏🬭🬞🬵🬹🬓🬓▌🬴 │ CPU: 42% Mem: 3.1 GB │
🬬🬨▐▐🬊🬕🬬🬱🬵🬍🬆🬂▌▌🬕🬕 │ Uptime: 14d 6h │
🬹🬹🬹▐🬹▌🬍🬬🬝🬄▐🬹▌🬓🬹🬹 │ Load: 1.2 0.8 0.5 │
🬨🬊▐▐🬻▌🬆🬂🬂🬊▐🬵▌▌🬄🬎 │ │
🬊🬬🬂🬁🬂🬀🬂🬂🬂🬂🬁🬂🬀🬂🬆🬆 │ │
🬁🬊🬎🬎🬊🬂🬎🬎🬎🬊🬆🬎🬎🬂 │ │
│ │
│ │
└────────────────────────────┘
┌────────────────────────────┐
🬭🬞🬇🬖🬞🬃🬓🬋🬖🬦🬹🬏🬏 │ │
🬦🬖🬷🬹🬶🬺🬻🬻🬻🬻🬻🬺🬷🬱▌🬏 │ System Status │
▐🬲🬄🬻🬹🬹🬏🬹🬷🬞🬵🬹🬓🬓🬟🬤 │ CPU: 42% Mem: 3.1 GB │
🬉🬷🬗▐🬊🬕🬬🬱🬵🬜🬆🬂▌🬀🬝🬐 │ Uptime: 14d 6h │
🬹🬺🬞▐🬹▌🬫🬬🬝🬄🬻🬹▌🬚🬉🬹 │ Load: 1.2 0.8 0.5 │
🬴🬗🬇▐█▌🬣🬎🬥🬗🬬🬦▌🬛🬉🬞 │ │
🬨🬇🬒🬁🬂🬀🬕🬥🬅🬕🬬🬂🬀🬣🬉🬝 │ │
🬂🬁🬎🬄🬉🬎🬄🬉🬁🬃🬈🬁🬎🬀 │ │
│ │
│ │
└────────────────────────────┘
┌────────────────────────────┐ ▄▄ ▀▀▀▀▀░░ ▄▄ │ │ ▀▀░░░░░░░░░░░▀▀ │ System Status │ ░░░░▀▀▀▀░▓▀▀▀░░░ │ CPU: 42% Mem: 3.1 GB │ ▀░░▀▀▀▀▀▒▀▀▀▀▀░░ │ Uptime: 14d 6h │ ▀░░▀▀▀▓▀▀▀▓▀▀▀░░ │ Load: 1.2 0.8 0.5 │ ░░░▀█▀▀░▀▀▓██░░░ │ │ ▀▀▀░▀▀░░░░▀▀▀░▀▀ │ │ ▀▀░░░░░░░░░░░░▀ │ │ │ │ │ │ └────────────────────────────┘
Same sextant mode, same resolution. The 256-color version uses dithering to approximate
truecolor gradients. The 16-color version (what you get on TERM=linux) uses
shade characters (░▒▓) to simulate tonal range with only 16 colors.
Override with COLORTERM=truecolor if your terminal actually supports it.
Common layout problems, what causes them, and how to fix them.
Symptom: nothing visible, no error.
<img> and <canvas> need width/height
as props, not style. Props define the pixel buffer. Style defines layout positioning. They are separate concepts.
<img src="photo.png" style="width: fill; height: fill" />
<img src="photo.png" width="fill" height="fill" />
Melker logs a runtime warning for this. The --lint flag also catches it.
Symptom: text cuts off at container edge.
Parent container has no overflow: scroll and content exceeds fixed height.
Scrollable containers also need a size constraint (flex: 1 or fixed height),
otherwise they grow to fit content and never scroll.
<container style="overflow: scroll">
<text>Long content...</text>
</container>
<container style="overflow: scroll; flex: 1; width: fill">
<text style="text-wrap: wrap; width: fill">Long content...</text>
</container>
Symptom: <select> or <combobox> fills the entire row.
Default cross-axis stretch in column flex layout. Standard flexbox behavior: in a column container,
align-items defaults to stretch.
Fix: wrap in a row container or add style="align-items: flex-start" to the parent.
Symptom: columns misaligned, text wraps at wrong position.
Emojis have inconsistent widths across terminals. Melker calculates emoji width as 2 characters, but some terminals render them wider or narrower.
Fix: use ASCII text instead. [OK] Success instead of emoji checkmark.
| Component | Fixed size | Responsive/fill |
|---|---|---|
| canvas | width={30} height={20} | N/A (buffer must be fixed) |
| img | width={30} height={20} | width="fill" or width="100%" |
| container | style="width: 30;" | style="width: fill;" |
| text | style="width: 40;" | style="width: fill;" |
| select | width={20} | width="50%" or width="fill" |
State binding, exported value copies, and two-way binding traps.
Melker has an optional state binding system via $melker.createState(). Bind an
element to a state key with bind="key", and state values push to elements
automatically on every render. Two-way binding (the default) also syncs user input back to
state without manual handler code:
<input id="query" bind="searchTerm" />
<text bind="count" bind-mode="one-way" />
<script>
const state = $melker.createState({ searchTerm: '', count: 0 });
// state.searchTerm auto-updates as the user types (two-way)
// state.count pushes to the text element (one-way)
</script>
Boolean state keys also toggle CSS classes on the root element, so conditional styling works without script:
.isEmpty #empty-message { display: flex; }
What it doesn't have: no dependency graph, no computed properties, no fine-grained reactivity
(Solid signals, React hooks). If element A's value depends on a computation involving state
keys X and Y, you write that computation in a handler. For most apps (5-15 interactive
elements), createState() + bind covers most cases. For apps
that don't use it, the system is skipped (early-exit guard).
Symptom: $app.count = 10 in a ready script, but the original variable stays at 0.
Exported primitive variables are copied by value onto $app. Setting
$app.count modifies the copy, not the module binding.
<script>
export let count = 0;
export function setCount(n) { count = n; }
</script>
<script async="ready">
$app.setCount(10); // modifies the original
</script>
Objects are copied by reference, so $app.config.debug = true does modify
the original. Only primitives (numbers, strings, booleans) have this problem.
Symptom: handler normalizes input (e.g., trim().toLowerCase()), but the value reverts on next render.
Two-way binding is the default. Before each render, reverse sync reads the raw input value back into state, overwriting the handler's normalized result.
<input bind="query" bind-mode="one-way" onInput="$app.normalize()" />
<script>
const state = $melker.createState({ query: '' });
export function normalize() {
state.query = $melker.getElementById('query').getValue().trim().toLowerCase();
}
</script>
Rule of thumb: if your handler modifies the same state key that the element is bound to,
use bind-mode="one-way".
Silent failures from missing policy entries.
Symptom: $melker.fetch() returns an error or hangs. No obvious message.
The policy is missing a "net" permission for the host.
Diagnosis: run with --log-level DEBUG --log-file /tmp/app.log, look for Access denied.
<policy>{"permissions": {"net": ["api.example.com"]}}</policy>
Or use --trust for quick testing (bypasses all policy checks).
Symptom: $ENV{MY_VAR} is empty, or Deno.env.get('MY_VAR') returns undefined.
Env vars are sandboxed. Only MELKER_*, HOME, PATH,
TERM, and XDG vars are auto-allowed.
Fix: add to policy "env": ["MY_VAR"] or use configSchema's env key for user-configurable values.
Font issues, protocol fallback, and sandbox detection.
Symptom: rectangles or tofu instead of smooth 2x3 block patterns.
Terminal font lacks Unicode 13.0 Symbols for Legacy Computing (U+1FB00-U+1FB3F).
melker --test-sextant
Fix: switch to a font with sextant support (Cascadia Code, Iosevka, JetBrains Mono), or override:
MELKER_GFX_MODE=quadrant melker app.melker
Symptom: blank area where image should be.
Running inside tmux/screen (protocol auto-disabled), or terminal doesn't support sixel.
Diagnosis: check --log-level DEBUG for sixel disabled messages.
Fix: run outside multiplexer, or use character-based modes instead.
Symptom: everything is grayscale, backgrounds invisible.
The sandbox terminal reports TERM=linux but only renders brightness, not hue.
Background colors are ignored.
What the engine does: detects IS_SANDBOX=yes and switches to
grayscale-compatible rendering.
A real app that hit several limits from sections 1-8. What happened, what worked around it.
The earthquake dashboard is a 431-line single-file app that fetches live USGS earthquake data, renders it in a sortable table with sparklines and distribution bars, and plots quakes on a world map. It was built across 8 commits over three weeks (Feb 12 to Mar 7 2026). This section traces which limits from the rest of this page showed up during development, and how they were handled.
data-table.setValue() with fetched JSON, one
line per component.
net: ["earthquake.usgs.gov"] and getting
sandboxed fetch with no extra code.
.melker file
and took more time than expected. This directly drove the creation of the
<tile-map> component (commit 476), which replaced ~35 lines of
coordinate data with a single element.
fillPolyColor, drawCircleCorrected, the tooltip system, and
canvas hit-testing. The dashboard was driving engine development, not using existing
features.
$melker.cache.read/write in the engine.
"net": ["earthquake.usgs.gov"],
then needed "map": true and "raw.githubusercontent.com" for tile
maps and plate data. Each new data source is a policy change discovered at runtime.
Color resolution matters more than spatial resolution. The dashboard looks good in truecolor
terminals, but under TERM=linux (16 colors) the map and magnitude coloring
lose most of their information density. You think the limit is character-grid resolution,
but it's actually color depth.
Deep dive: How It Works · Dashboard source