Octomind
Same model as opencode, same endpoint, same prices — octomind solved 24/25 to its 19/25, at half the cost. Open-source agent runtime: one binary, any model, runs unattended in CI. Apache 2.0.
翻譯準備中
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證A small Mac app for adjusting self-contained HTML presentations and reports without opening a code editor. It splices edits into the source rather than serialising the DOM, so the file survives.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Formerly Nudge: renamed in 2.9 to sit with Trellis and Sage. Same app; it installs alongside an old copy of Nudge rather than replacing it, with its own backups folder.
A small Mac app for adjusting self-contained HTML presentations and reports without coming back to Claude for every tweak.
Two modes, switched with ⌘E:
Version 2.9.
--fontcheck checks every weight, not just the family name, so a copy whose
weights all collapse onto one face is caught too.Almost every "edit HTML visually" tool sets contenteditable and then reads back
document.documentElement.outerHTML. That is unsafe for decks like these.
These files run their own JavaScript. A deck's navigation script does things like:
count.textContent = (i + 1) + ' / ' + slides.length;
where.innerHTML = sect + ' · <b>' + label + '</b>';
slides.forEach(function (s, x) { s.hidden = doc ? false : x !== i; });
Serialising the DOM after that has run bakes the current state into the file
permanently: save while viewing slide 3 and every other slide gets a hidden
attribute. The deck is destroyed. The slide counter freezes. Inline styles that
the script set become part of the document.
So Tend never serialises a DOM. It:
<script>,
<style>, comments and markup).Everything you did not edit is preserved byte for byte: CSS, JavaScript, indentation, comments, attribute order, entity spelling.
Wording mode
alt, aria-label, title,
aria-description, placeholder, data-label<title> element and <meta name="description">href), under their own filter so they stay out of the wayLayout mode
Setting a height can hide content, but only where the element actually clips:
overflow: visible lets it spill and stay readable, which is how plenty of these
decks are built. Warning on scrollHeight > clientHeight alone cried wolf on
untouched slides, so the warning needs both a height Tend has set and an element
that really clips.
Layout changes are written as inline declarations on the specific element you selected. Nothing touches the stylesheet, so one adjustment can never quietly restyle the other fourteen slides.
The interface never says <dd>, aria-label or div.stat-card at anyone. Markup
is translated into words a salesperson would use: Title, Paragraph, Value, Button,
Image description, Screen-reader label. The precise markup is still there in a
tooltip for whoever wants it.
Three things follow from that:
Three pillars: a <b>lower engineering cost</b>, and … into seven separate runs. Correct for
editing, nonsense to read, so the list shows the whole sentence as one row and
says how many pieces it has. Clicking the slide, searching, and Tab still reach
every individual piece.There is a short guide under Help, shown once the first time a file is opened.
Title hard left, the Wording / Layout switch centred on the window, and the actions including Open on the right.
There is no app icon in it. The icon is already in the Dock, the app switcher and Mission Control; inside the app's own window it says nothing new and competes with the one thing that strip is for, which is telling you what file you are in. The CK mark in the bottom corner is the only mark in the window.
The system toolbar is gone. Its small grey glyphs sat oddly against everything else, and it could not be restyled. In its place is a bar in the app's own language: the file being edited, a Wording / Layout pill, and the handful of actions worth having in reach, with Save as the site's cyan call to action when there is something to save.
Inline markup splits Three pillars: a <b>lower cost</b>, and … into several byte
ranges with tags between them. The editor shows the sentence entire; the change is
located by comparing against the old text and applied only to the runs it actually
touches, so the emphasis on the parts you leave alone survives. Rewrite the whole
thing and the emphasis goes, which is what you asked for.
Edits store a run's complete replacement text rather than a trimmed core with the
original whitespace put back around it. Reattaching was wrong whenever an edit
deleted the space at a seam: it reappeared, and entirely. came back as
entirely .
The two modes used to keep entirely separate selections, so switching threw you back to wherever you last were in the other one, usually a different slide. They are two views of the same piece of the deck, so the selection is handed across: wording to layout takes whatever the styling controls were pointed at, and layout to wording takes a string from inside the selected element.
Selecting a picture is the case that needs care, because it holds no words at all. Rather than leave wording pointing at some earlier choice on another slide, it widens outwards until it finds a string, and settles for anything on the same slide before it gives up. Landing on the right slide matters more than landing on the right word.
Fifteen slides' worth of items at once is a wall. The section you are working on is open and the rest are shut, following your selection and the deck as it moves. Click any heading to open it yourself. Searching opens everything, because the point of searching is to see matches wherever they are.
Its buttons and links keep working while you edit, so it stays navigable. To edit the wording on one of them, hold ⌥ and click.
Pressing Next in the deck, or using its own keyboard shortcuts, scrolls the list to that slide. It only scrolls: it will not steal your selection part-way through an edit. Both kinds of deck are handled, the sort that hides all but the current slide and the sort you scroll through.
In Wording mode the list is every piece of text, grouped by slide. In Layout mode it becomes an outline of each slide's pieces — its cards, columns, headings and images — indented to show what sits inside what, with a snippet of the words each one contains. Picking anything from either list takes the preview to that slide, using the deck's own navigation controls where it has them so its slide counter stays in step.
Alignment, weight and colour live under the editing box in Wording mode, not in Layout. Someone rewriting a line is the same person who wants it bolder or in the other brand colour, and making them change mode to do it broke one job in half. Layout is left holding geometry alone: size, spacing, position, order.
Size. Text size sits here rather than with layout for the same reason the rest does: it is a property of the words, not of the box around them. Layout is left holding geometry alone, and its Size card is width and height.
Colour. A well that opens the system picker, and beside it the colours this deck already uses, commonest first, sampled from what is actually rendered. Picking from those keeps an edit on the deck's palette rather than introducing a stray colour that only looks right on one slide. Text ▸ Reset Text Colour hands the element back to the stylesheet.
Weight. Only the weights the element's own font really has. The faces the deck
embedded are enumerated from document.fonts, and a variable font's range is
expanded to the steps inside it — one deck offers 400 to 800 and nothing else.
Offering 600 where only 400 and 700 exist buys a browser-synthesised weight that
looks wrong on screen and worse in print. Where the font is not embedded at all,
the usual ladder is offered and the tooltip says why.
Who it lands on. A "Change" menu names the exact words a change will reach, and lets you pick a level: the bold phrase on its own, whatever holds it, out as far as the paragraph. Each option carries a quote of the words it covers, so the choice is never ambiguous.
The list has two sources. What the cursor is actually inside comes first, because
that is nearly always what a change is meant for, and a cursor sitting inside
deliberate emphasis markup (b, strong, em, i, mark, code, a) selects
that level by default. Anything else the sentence marks up for emphasis is on the
list too, so the control does not quietly vanish whenever the cursor happens to be
resting in the plain words between two bold ones. A bare <span> never grabs the
default: a wrapper is not evidence of intent.
Deciding when to re-read that costs a DOM round trip, so it is guarded — and the guard has to include which level the cursor is in, not just which levels exist. Comparing the list alone, moving from the plain words into the bold looks like no change at all, which is precisely the case the feature is for.
Alignment stands down whenever the chosen level is an inline box, because
text-align applies to block containers: set on a <b> it writes perfectly
cleanly and does nothing whatsoever.
Colour and weight both change whether text is readable — weight moves the WCAG large-text boundary, so going lighter can fail text that passed at bold without its colour changing at all. Contrast is recomputed and reported after every change to either.
Strings that never reach a slide, the document title above all, would take styling happily and show nothing for it. The controls stay dark for those.
A sun/moon toggle at the right of the bar, drawn the way the one on the site is: a bordered button holding a 24-unit line icon in the link blue, showing the mode you are in. The choice is remembered between launches. View ▸ Match System Appearance hands the decision back to the Mac.
Dark mode uses the site's own #0B1F2A as its ground rather than an invented
palette, and every colour is measured in both appearances by the suite.
Type size is one number. Every size in the app goes through Palette.font, which
applies a single scale, so giving the whole interface more room to breathe is one
change rather than a hundred call sites drifting apart. Control bezels are sized by
controlSize rather than by the font inside them, so those moved up with it: bigger
type in a bezel built for smaller type is cramped, not generous.
Bigger type is only an improvement if it still fits, so the suite sweeps every label in the window and fails on any whose content needs more height, or more width than a non-truncating label has. Contrast and overflow checks both pass happily on text jammed against its frame.
The palette, type and card treatment come from chriskeene.me: dark navy ink on a
pale blue ground, a deep blue accent, white cards with a hairline border, and small
uppercase letter-spaced labels in the accent colour. Atkinson Hyperlegible Next
throughout. The faces ship inside the app and are registered before anything is
drawn, so it looks the same on a Mac that has never had the font. Running the app
with --fontcheck confirms that, weight by weight. The arrows on Layout's Up and
Down buttons are not in the typeface, so AppKit draws those from the system font.
Two places the site's values needed adapting rather than copying, both caught by measuring:
#CDE7F7) is drawn on white. On a tinted panel it measured
1.16:1 and the cards stopped reading as cards, so the border is a step stronger.#51687A) is used at 16px there. At 10.5px on a selected row it
fell to 3.7:1, so supporting text is a step deeper. That in turn let the
selection tint be strong enough to actually see: the pale original was 1.1:1
against the sidebar, effectively invisible.The current row is marked with a rule of accent down its left edge and a quiet
tint, matching the slide headings, rather than the source list's saturated accent
pill: that pill ignores drawSelection(in:) entirely, so selection drawing had to
be taken away from AppKit with selectionHighlightStyle = .none.
Slide headings in the list are drawn by hand rather than as AppKit group rows, because AppKit restyles those with its own washed-out label colour and will not be overridden. A compliant colour was being specified and a non-compliant one was reaching the screen, which is why the suite now measures rendered pixels too.
AppKit's secondaryLabelColor and tertiaryLabelColor measure 3.8:1 and 1.9:1 on
a standard window background: fine by Apple's HIG, a 1.4.3 failure for the small
text this app uses them for. systemOrange is 1.9:1 and unusable for a warning.
Sources/Palette.swift holds values that pass, and ./run-tests.sh measures every
one of them against the surface it actually sits on, in both light and dark. An
accessibility company's own tool should not fail the checks it sells.
These decks are built from CSS Grid and Flexbox — one has 32 grids and 22 flex containers across 22 grid templates. Content has no x/y coordinate; its position is a consequence of flow. Absolutely positioning things so they could be dragged would break the responsive and print behaviour, and would decouple visual order from DOM order — a WCAG 1.3.2 reading-order failure, which is not a good look in an accessibility deck.
Spacing alone does not cover it either. Padding and margins change an item's own box, so the row re-flows and everything beside it shifts too. That is exactly wrong for an icon sitting a couple of pixels high in a flex row.
So Position writes a paint-time offset: the item is drawn somewhere else while the layout carries on as though it had not moved. Nothing beside it shifts, the deck still responds and prints, and the DOM order a screen reader follows is untouched. Two techniques, chosen from what the browser actually resolved:
translate for anything with a box of its own. It is the standalone CSS
property rather than transform, so it composes with a transform the deck
already set instead of clobbering it.position: relative with left/top for a plain inline element, because
transforms do not apply to a non-replaced inline box at all — a translate on a
<span> is silently ignored. An absolutely placed element goes back to
translate, since there left and top are its coordinates rather than an
offset from them.Inside an SVG the rule inverts: CSS positioning does not apply to SVG elements at
all, while transforms do, and a shape reports display: inline — so the naive rule
sent an icon's own paths down the left/top branch and they refused to move. The
namespace is read from the DOM rather than guessed from tag names. Note that a
translate there is in the icon's user units, so a viewBox scale applies: in one
deck a 6px step moves an icon's path 2.7px on screen.
Clicking an icon now selects the icon, not the single <path> under the
cursor: elementFromPoint returns the deepest hit, and nobody means "resize this
path". <text> and <tspan> are the exception, because a label in an SVG chart is
worth selecting on its own. Inner shapes stay reachable from the outline.
The offset Tend reports is what it wrote inline, not the computed value, so
whatever the stylesheet already does stays the baseline it is measured from. "Put
back" clears all of it in a single undo step, and only takes back a
position: relative that Tend itself added — one the file declares is the
author's and is left alone.
The controls are two arrow pairs, ← → and ↑ ↓, with no words beside them.
"Across" and "Down" were accurate and meant nothing until you already knew what
they did; an arrow says which way it goes without being read.
Reorder among siblings is still there, and still the right answer when the order itself is wrong rather than the position.
Significant whitespace. A run like Fixes before an inline <b> carries the
word gap in its trailing space. Each run is split into leading whitespace, an
editable core, and trailing whitespace; you edit the core and the affixes are
reattached on save. So the space survives, and so does the file's own indentation.
Character encoding. A file:// page with no <meta charset> is decoded by the
browser as windows-1252, turning · into · and € into €. Tend pins the
preview to UTF-8 so what you see is right, and warns you when the file itself is
missing the declaration — with File ▸ Add UTF-8 Declaration to fix it.
Hidden slides. Selecting text on a slide that isn't showing removes the
hidden attribute from its ancestors, which restores whatever display the deck's
own CSS specifies. Forcing display: block would break decks whose slides are
grid or flex.
Fixed-width slides. A deck whose slides are a fixed pixel width scales itself to the viewport, but when the viewport is narrower it overflows and gets clipped (reporting no scroll overflow at all, so it's invisible to the obvious check). Tend measures the widest pre-transform layout width and zooms out so the CSS viewport reaches it.
Source line wrapping. A paragraph the author wrapped over four indented lines
is formatting, not content, so the editor shows it as one flowing paragraph rather
than reproducing the file's indentation. Where whitespace genuinely is content —
<pre>, or anything computing to white-space: pre, pre-wrap, pre-line or
break-spaces — it is left byte-for-byte alone. That distinction is read from the
live DOM's computed style, not guessed from tag names, so a pre-wrap set by a CSS
class is caught too. Non-breaking spaces are never collapsed; they are content.
Mis-nested markup. If the source has crossed or unbalanced tags, an element's byte range is not a self-contained fragment and moving it would produce garbage. The scanner marks anything it had to recover from, and layout mode refuses structural edits on it, saying why. Wording edits stay available throughout, because those are offset splices that do not care about nesting.
Duplicate ids. Copying markup verbatim would mint duplicate ids — invalid
HTML that breaks anchor links and aria-labelledby, since lookups return only the
first match. Duplicating renames the copy's ids and repoints the references inside
it (href="#…", for, aria-labelledby, aria-describedby, aria-controls,
aria-owns, headers).
Contrast when text shrinks. WCAG's threshold moves: large text needs 3:1, smaller text needs 4.5:1. Shrinking a heading can cross that boundary and turn a pass into a fail. Layout mode computes the contrast after each size change and says so. It measures the rendered size, after any transform scaling, because a deck drawn at 1280px and scaled to fit renders 30px text at 24px. Where a gradient or image sits behind the text it reports the reading as unreliable instead of crying wolf.
Entities. — and friends are decoded for editing and re-escaped
minimally on save. Only &, <, > and non-breaking spaces are escaped, so
edited markup still reads like it was written by hand.
⌘O / ⌘S | Open / Save |
⌘R | Review changes before saving |
⌘Z / ⇧⌘Z | Undo / Redo |
⌘F | Jump to the search box |
⌥⌘F | Replace across the file |
Tab / ⇧Tab | Next / previous string, while editing |
⌥⌘↓ / ⌥⌘↑ | Next / previous string, from anywhere |
⌘1–⌘5 | Filter: All, Prose, Labels, Links, Changed |
⌥⌘S | Hide the sidebar, for more slide room |
⌘+ / ⌘− / ⌘0 | Zoom the preview |
⇧⌘A | Show every slide at once, for a read-through |
⌘E | Switch between Wording and Layout |
⌘? | How to use Tend |
Every layout action has a menu command, because AppKit controls only join the tab loop when Full Keyboard Access is on.
⌃⌘] / ⌃⌘[ | Wider / narrower |
⌃⌘' / ⌃⌘; | Taller / shorter |
⌃⌘. / ⌃⌘, | More / less padding |
⌥⌘← ⌥⌘→ ⌥⌘↑ ⌥⌘↓ | Shift the item, 2px a press, neighbours untouched |
| ⌃⌘↑ / ⌃⌘↓ | Move earlier / later among siblings |
| ⌃⌘← | Select the containing element |
| ⌘D / ⌘⌫ | Duplicate / delete |
⇧⌘. / ⇧⌘, | Bigger / smaller text |
⌘B / ⌥⌘B | Bolder / lighter, to the next weight the font really has |
| Text menu | Align left, centre, right; reset text colour |
A full wording pass is: click the first string, then type, Tab, type, Tab.
⌘R) shows every pending edit, old against new, before anything
is written.~/Library/Application Support/Tend/Backups on every save.Needs only the Xcode Command Line Tools — no Xcode project, no dependencies.
./build.sh
Produces build/Tend.app. Copy it to ~/Applications or /Applications.
| File | |
|---|---|
Sources/HTMLScanner.swift | Offset-tracking tokeniser; finds editable ranges and the element tree |
Sources/ElementModel.swift | Elements with byte ranges, inline-style parsing, DOM readings |
Sources/HeaderBar.swift | The top bar, brand buttons and the segmented pill |
Sources/LayoutBar.swift | The layout controls |
Sources/TextStyleStrip.swift | Alignment, weight and colour, under the editor |
Sources/Vocabulary.swift | Markup translated into plain English |
Sources/Palette.swift | Brand colours, type and the card component |
Resources/AppIcon.icns | The app icon, generated by a script in the repo |
Sources/HelpSheet.swift | The short guide |
Sources/Entities.swift | Entity decode/encode, whitespace affix splitting |
Sources/Aligner.swift | Sequence alignment between DOM text nodes and source runs |
Sources/DocumentModel.swift | Edits, undo, splicing, saving, backups |
Sources/Bridge.swift | Injected JavaScript: click mapping, highlight, reveal |
Sources/UI.swift | Window, sidebar, preview, editor bar |
Sources/ReviewSheet.swift | The before/after confirmation sheet |
Sources/main.swift | App delegate, menus, toolbar |