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…

495 ·GitHub
What is this?Noty is a native macOS sticky-notes app that hides at the edge of your screen as a small pill, fanning out into note tabs when you move the pointer there, instead of sitting in a dock or window.
Is this a menu-bar app, or does it even have a dock icon?It has neither a dock icon nor a menu-bar icon in the usual sense; it lives as a thin 'pill' on a screen edge and only appears when you hover there, drawing a main menu purely to make standard key shortcuts work.
How do I get it running?Download the latest DMG from the noty-sepia.vercel.app site, install it, and slide your pointer to the screen edge to see the note deck.
Do I need an Accessibility permission or any extra setup for the shortcuts?No special permissions are needed — global shortcuts are registered through the Carbon hotkey API, so Accessibility access isn't required.
Is editing synced notes on my iPhone safe with any Markdown app?Only editors that save in place work, because sync detects changes via the file's modification date; apps that save through an 'Open in...' share sheet create a duplicate file that breaks syncing. Taio, Runestone, Textastic and Obsidian are recommended.
Does it work across multiple monitors?Yes — you can show the deck on all displays, just the main screen, or a specific monitor, and drag the pill to dock it to any edge, height, or display.
Is it available on iPhone/iPad too, or Mac only?The app itself is Mac-only, but notes can sync to iPhone/iPad as plain Markdown files via iCloud Drive, readable and editable there with any editor that saves in place.
Can I turn a line into a checklist item?Yes, ⌘T turns the current line into a task with a checkbox; it exports as standard - [ ] / - [x] Markdown and All Notes shows a done/total count per note.
Can I paste or drop images into a note?Yes — paste an image, drag a file in, or use Insert Image; it renders inline, is resizable via a drag handle, and is stored as a file under ~/Library/Application Support/Noty/Images/, deleted when no note references it anymore.
Can I automate creating notes from Shortcuts or a terminal?Yes, Noty registers a noty:// URL scheme that Shortcuts, Raycast, Alfred or a terminal can call; the passed text only ever becomes note content, never executed code.
What happens if I drag a note tab off the deck?It detaches into a floating, resizable sticky window you can move across displays; left idle a minute it tucks itself back into the deck, and pinning it keeps it always on top and un-tuckable.
What are similar projects?tendedero, WakeGPT, OpenYoink, forel, mactap-app, pliss, sill, zulu
Creator
aimen08
Size
18.5 MB
Stars
495
Age
1mo
Commits · 90 Days