noty README

The project's README from github.com/aimen08/noty. As one markdown file: README.md.

Noty

Sticky notes that live at the edge of your screen. A native macOS app in Swift, SwiftUI and AppKit.

No dock icon, no window to manage. Slide the pointer to the right edge and the deck fans out.

noty-sepia.vercel.app · Download the latest DMG

Noty in use: the deck fans out from the screen edge, a checklist is pulled open, two tasks are ticked off, and the note is dismissed by clicking away

At rest Fanned A note pulled open
The deck at rest: a thin pill of coloured dashes on the screen edge The deck fanned into shingled paper tabs A note pulled clear of the deck, carrying its tab down the left side

Three states, one movement

State What you see Trigger
Rest A 12 pt pill on the screen edge — one coloured dash per note idle
Fan Notes shingle down the edge 45 ms apart as vertical tabs, each keeping its colour and carrying its label turned on its side pointer enters the pill
Expanded The note slides clear of the deck at full size, level with its own tab, which stays visible behind it click a tab

The deck shows five notes at a time and puts the rest behind a +N tab, which opens the full list in a scrolling deck. A + button sits in the bottom corner while the deck is open.

Tabs shingle: each one is full height but sits only a strip below the one before, lapping over it like a roof tile. That is what keeps the deck to roughly half the screen height instead of running its full length. The strip is sized to the longest title currently on the deck, so labels read in full and ellipsise cleanly rather than being clipped mid-word.

The fan is only 56 pt wide, so the deck covers almost none of what is behind it.

Two deck styles

Right-click the pill → Deck style:

Labelled tabs Full-size tabs carrying their titles — the default
Colour chips Colour only, no labels; the deck shrinks to a row of chips

The open note carries its own tab along as a left gutter, separated by a dashed rule, so it reads as growing out of the deck rather than floating beside it.

Deck size scales every metric off one multiplier — tab width, the lap between tabs, the label type, the chips and the resting pill — so the deck grows without drifting out of proportion with itself. Settings → Deck → Size (70–180%), or right-click the pill → Deck size for the four presets.

Keep deck open makes the fan the resting state instead of the pill, so the tabs and their labels stay on the edge without being hovered first. Off by default; notes still open, close and idle away exactly as before.

Automation

Noty registers the noty:// URL scheme, so Shortcuts, Raycast, Alfred or a terminal can talk to it:

open "noty://new?text=Buy%20milk"   # create a note with this text
open "noty://capture"               # open the quick-capture box
open "noty://all"                   # the All Notes window
open "noty://settings"              # Settings

The text only ever becomes note content — nothing in a URL is executed.

Shortcuts

All of these are listed in Settings (⌘,), and the first four can be rebound there. Global ones are registered through the Carbon hotkey API, so no Accessibility permission is required:

⌥⌘N new note (opens it straight away)
⇧⌘Space quick capture — jot without opening the editor
⌥⌘A All Notes
⌥⌘L the Archive window

Inside a note:

Esc close the note, back to the tabs (or dismiss the find bar)
⌘F find in note
⌘. cycle its colour
⌘T turn the current line into a task, or strip the checkbox off
⌘⌫ delete, with ten seconds to undo
⇧⌘A archive the note you are looking at
— every shortcut here can be rebound in Settings
⌘P pin the note so it stays open
⌃+ / ⌃- bigger / smaller note text

Standard editing (⌘C / ⌘V / ⌘Z / ⌘A) works everywhere — the app builds a main menu at launch purely so those key equivalents dispatch, even though an accessory app draws no menu bar.

Checkbox tasks

Any line can be a task. ⌘T (or the checklist button in a note's header) puts a checkbox on the current line; click the box to tick it off and the line is struck through and dimmed. Return carries the list on to the next task, and Return on an empty task ends the list.

Tasks live inline in the note body as ☐ / ☑ prefixes, so a note stays plain text. Markdown export writes them as standard - [ ] / - [x] task syntax and import reads that back. All Notes shows a done/total count per note.

Images

Paste (⌘V with an image on the clipboard), drag an image file in, or right-click → Insert Image…. The picture renders inline at full width by default; underneath it the body keeps a one-line token, ![image](noty-img://<UUID>). Unlike the other markdown markers the token never surfaces just because the caret is near — clicking the image parks the caret right after it, and pressing delete there reveals the token, fully selected, as a confirmation step: delete again to remove the image, or type, press Return/Space, paste or click elsewhere to keep it and the picture comes straight back.

Hover an image for a drag handle at its bottom-right corner; drag to resize, and the height follows the aspect ratio. The width is written back into the token — ![image|300](noty-img://…) — so sizes survive restarts and export/import.

Images live as files under ~/Library/Application Support/Noty/Images/ and are deleted when the last note referencing them is deleted. .stickies export archives carry them as base64, so a round-trip keeps every picture.

Everything else

On the phone

Reading needs nothing: tap a file in Files and Quick Look renders the Markdown. Editing needs an editor that writes in place, because sync reads the file's own modification date to notice a change. Taio is free and a good starting point; Runestone (free), Textastic and Obsidian (Open folder as vault → iCloud Drive/Noty) all edit in place too.

Avoid anything that takes a copy through an "Open in…" share sheet. Saving then leaves a second file carrying the same noty-id, which is not what you meant and not something Noty can make sense of. Opening the file from the editor's own Files browser avoids this.

To write a note on the phone, make an ordinary .md file in the Noty folder — no header, one line of text is enough. The Mac picks it up on its next pass, generates an id, writes the header back, and the note joins the deck under the filename you chose, titled by its first line. Tasks written as - [ ] / - [x] become ☐ / ☑ on the deck and come back as Markdown. A first line of --- is safe; it is not mistaken for a header.

Two things worth knowing. The sync engine lives in the Mac app, so a file written on the phone is picked up when the Mac is running, or next time it starts. And Noty/Conflicts/ is an archive of versions that lost a conflict — it is there to be read and pruned by hand, and never syncs back.

Your notes stay on your Mac

Verify it yourself:

sqlite3 ~/Library/Application\ Support/Noty/notes.db "SELECT hex(substr(body,1,24)) FROM notes;"
strings ~/Library/Application\ Support/Noty/notes.db | grep "some text from a note body"   # finds nothing

Build

Requires the Swift toolchain from Command Line Tools (Xcode is not needed) and macOS 15+. A local release build produces a universal app; releases ship one DMG per architecture instead — Noty.dmg for Apple Silicon and Noty-intel.dmg for Intel, each a third smaller than a universal image, with its own Sparkle feed (appcast.xml / appcast-intel.xml) baked into the build so updates stay on the right architecture.

./scripts/fetch-sparkle.sh   # once — pulls the Sparkle binary framework
./build.sh                   # release build → build/Noty.app
./build.sh debug             # fast, unoptimised
./build.sh release run       # build, then relaunch
./scripts/test-editor.sh     # focused editor range/style/input regression checks
open build/Noty.app

build.sh drives swiftc directly over Sources/*.swift for both supported architectures, combines the resulting binaries with lipo, assembles the .app bundle around Info.plist, embeds Sparkle, and signs it.

Sparkle is optional. Without Sparkle/Sparkle.framework the app still builds — Updater.swift compiles to a stub and the update menu says so.

Branches

dev is where work lands — every pull request targets it, and CI builds it. main is the release branch: merging dev into main is what cuts a release, so nothing reaches main until it has been tried.

Releasing

git checkout main && git merge --no-ff dev -m "release: 1.2.0"
git push origin main

That fires .github/workflows/release.yml, which builds the app, packages a DMG, signs it with the EdDSA key, writes appcast.xml, publishes a GitHub Release and commits the appcast so installed copies can see the update.

The version comes from release: X.Y.Z anywhere in the merge commit message. Leave it out and the patch is bumped from the newest tag, which is what a hotfix wants. A version that is already tagged is a no-op, so re-running the job is safe.

Release notes are written by scripts/make-release-notes.sh from the pull requests the release actually contains — matched by whether their merge commit is reachable, not by date, since work merged into dev sits unreleased until dev lands. Every one of them is listed with a link and its author, on the release page and in the Sparkle update dialog. Run it yourself to see what the next release will say:

VERSION=1.2.0 ./scripts/make-release-notes.sh

Work that ships without a pull request of its own being merged — a cherry-pick, or a branch only part of which was taken — is credited with a trailer on the commit that takes it, which the same script turns into a Thanks section:

Thanks-to: @handle — what it was for

Documentation, the site, the media and the appcast are excluded from the trigger, so a README push to main never cuts a release. workflow_dispatch with a version is the manual escape hatch.

It needs one repository secret, SPARKLE_PRIVATE_KEY — the contents of the key scripts/fetch-sparkle.sh's toolchain generated. The matching public key is already in Info.plist as SUPublicEDKey; an update signed by any other key is refused by the installed app.

To build a DMG by hand:

./build.sh release && ./scripts/make-dmg.sh 1.0.1

Signing note. Sparkle ships signed by its own team, and dyld refuses to load a framework whose Team ID differs from the process — so build.sh re-signs the framework (innermost bundle first) with the same identity as the app. Set CODESIGN_IDENTITY to use a Developer ID instead of an ad-hoc signature.

Layout

Sources/
  Main.swift            @main entry point; NSApplication, accessory policy
  AppDelegate.swift     wiring, actions, main menu
  Core.swift            Paths, AES-GCM Crypto, colour palette, Note model
  Store.swift           SQLite (C API) — schema, load, upsert, delete
  NoteStore.swift       observable model; the single source of truth
  Settings.swift        UserDefaults prefs + launch-at-login
  HotKeys.swift         Carbon global shortcuts (no permissions)
  DeckPanel.swift       geometry, NSPanel subclass, tracking container
  DeckController.swift  one deck per display + the state machine
  DeckViews.swift       pill, fan, tabs, the 45 ms stagger
  NoteEditor.swift      NSTextView bridge, find, 250 ms autosave
  LibraryWindow.swift   All Notes / Archive
  ExportImport.swift    md / txt / single file / .stickies
<<<<<<< HEAD
  NoteDocument.swift    the front-matter markdown form of a note
  CloudFolder.swift     the iCloud Drive folder: paths, names, coordinated I/O
  CloudSyncIndex.swift  what the last sync pass saw
  SyncPlan.swift        pure decision table: notes + files + index → actions
  CloudSync.swift       runs the actions, schedules the passes
=======
  ImageStore.swift      on-disk image files, noty-img:// token helpers
>>>>>>> pr35
  UndoToast.swift       the ten-second undo after a delete

Four details worth knowing if you touch the deck:

noty (aimen08/noty)

Searching Database…

496 ·GitHub
这是什么?Noty 是一款原生 macOS 便签应用,它以一个小胶囊的形态隐藏在屏幕边缘,当你把指针移过去时会展开成便签标签页,而不是待在 Dock 或窗口里。
这是菜单栏应用吗?它到底有没有 Dock 图标?它既没有通常意义上的 Dock 图标,也没有菜单栏图标;它以细长的「胶囊」形态存在于屏幕边缘,只有指针悬停在那里时才出现,绘制主菜单纯粹是为了让标准快捷键生效。
怎么让它跑起来?从 noty-sepia.vercel.app 网站下载最新的 DMG,安装后把指针滑到屏幕边缘就能看到便签组。
快捷键需要辅助功能权限或其他额外设置吗?不需要任何特殊权限——全局快捷键通过 Carbon 热键 API 注册,因此无需辅助功能权限。
在 iPhone 上用任意 Markdown 应用编辑同步的便签安全吗?只有原地保存的编辑器才行,因为同步是靠文件的修改日期来检测变化的;通过「打开方式…」分享面板保存的应用会创建重复文件,破坏同步。推荐 Taio、Runestone、Textastic 和 Obsidian。
它支持多显示器吗?支持——你可以让便签组显示在所有显示器上、只在主屏显示,或显示在指定显示器上,还可以拖动胶囊把它停靠到任意边缘、高度或显示器。
它在 iPhone/iPad 上也有吗,还是仅限 Mac?应用本身仅限 Mac,但便签可以通过 iCloud Drive 以纯 Markdown 文件的形式同步到 iPhone/iPad,在任何原地保存的编辑器中都能查看和编辑。
我能把一行变成清单项吗?可以。⌘T 会把当前行变成带复选框的任务;它导出为标准的 - [ ] / - [x] Markdown,All Notes 里还会显示每个便签的已完成/总数计数。
我能把图片粘贴或拖放进便签吗?可以——粘贴图片、拖入文件或使用 Insert Image 都行;图片会内联显示,可通过拖拽手柄调整大小,并以文件形式存储在 ~/Library/Application Support/Noty/Images/ 下,当没有便签再引用它时会被删除。
我能通过快捷指令或终端自动创建便签吗?可以。Noty 注册了 noty:// URL scheme,快捷指令、Raycast、Alfred 或终端都可以调用;传入的文本只会成为便签内容,绝不会是被执行的代码。
如果我把便签标签从便签组里拖出来会怎样?它会分离成一个可浮动、可调整大小的便签窗口,可以跨显示器移动;闲置一分钟后它会自动收回便签组,而固定(pin)之后它会始终置顶且无法收回。
What are similar projects?tendedero, WakeGPT, OpenYoink, forel, mactap-app, dimble, sill, zulu
Creator
aimen08
Size
18.5 MB
Stars
496
Age
1mo
Commits · 90 Days