Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧶 Loose Ends

Intent-first window triage for Safari. Every open window is a loose end — something Past You meant to do. This ties them off.

▶ Try the live demo — the real dashboard running on simulated tabs. Write notes, hand out verdicts, watch a window turn green, close it (sound included). Nothing real is touched; the whole thing resets on reload.

The loop: write a why-note, give a verdict, the window turns green, close it — the intent survives


Get it

You're on Path Status
Safari, today The localhost app — three lines of terminal, zero dependencies ✅ Works now
Safari, one-click app Mac App Store 🕐 Submission in progress
Chrome / Edge / Brave Chrome Web Store 🕐 Submission in progress — or load it unpacked today
Firefox Load as a temporary add-on ✅ Works now
Just looking The live demo ✅ In your browser

Both flavors share the same window-blind clustering and the same rule that nothing closes without your click. The localhost app reads Safari via AppleScript; the extension swaps that for the browser.tabs API — on Chrome it asks only for tabs + storage, while Safari additionally requires "Allow on Every Website" to read tab titles (why). Store links will replace the 🕐 rows as each goes live.


Why this exists

I build tools that fit how I actually work, and this project is me opening up about that. There's no B2B AI SaaS goal here. It's just: this is how I work, and this is how it could be just a bit better.

Who even uses Safari? Hello — me. Too many Chrome windows will straight-up crash my computer; Safari stays light no matter how bad my window situation gets, and every one of them syncs to my iPhone. (Does anyone even use Chrome on their iPhone?) Hello, locked into the Apple ecosystem — and honestly, comfortable here. The tradeoff is that nobody builds extensions for us. So I build my own.

tab-out answers "what do I have open?" Loose Ends answers "why is it open, and how do I close it without losing the intent?" (More on tab-out below — it's the reason this exists.)

Every Safari window exists because Past You wanted something. The window is the to-do; that's why you can't close it. Loose Ends lets each tab's intent become a note + verdict + (optional) date, so the window can close and the intent survives.

Inventory → intent → action.

Topics, not containers

A window is just where things happened to land. Tangents open in whatever window is frontmost, and one real topic scatters across many windows. So Loose Ends clusters topic-first and window-blind — a "Ditto" search, an eBay listing, and a plush page cluster together even though they're in three different windows. Windows show up only as physical metadata: a small W12 badge on each tab, and the thing you actually close.

Topics view: one Ditto Plush topic gathered from windows 1, 2, and 4 — the W badges are the only place windows appear


What it does

  • Topic cards, not tab soup. The primary view clusters your tabs by topic, window-blind. No AI — just repeated-phrase detection over your tab titles and search queries (the intent you typed).
  • Why-notes. One field per tab: why is this open? The note is the product.
  • Verdicts. To-do · Read · Date · Done · Let go. One click.
  • Safe-to-close, by window. In the Windows view, a window turns green once every tab in it is settled; the green ones float up into a "Ready to close" shelf at the top. Green means you may close it — nothing is ever closed by the machine.
  • One loop, one home per verdict. The sidebar runs in three groups. TriageTopics (what is all this?), By category (a second lens on the same tabs: Dev, Shopping, Reading, … when topic clusters aren't the grouping you need), and Windows (close what's settled). Queues, where every verdict lands in exactly one place — To-dos (actions, grouped by topic), Reading (the read-later pile), Calendar (a month grid of the dated ones: overdue and today are marked, click a day to filter), Archive (what you tied off). Then Set aside, below.
  • Notes filter. All · Has note · No note over the card views, so you can see which tabs actually carry a why-note. It's a lens, not a recount — the meters and tab counts still reflect the whole section.
  • Guided first run. A live 3-step checklist teaches the note → verdict → green-window → close loop on your real tabs, then disappears for good.
  • Export as AI brief. One button per topic cluster copies its tabs — URLs, your notes, your verdicts — as a markdown brief you can paste into any AI: "help me knock out these loose ends." No API key, no network call, no LLM in the app; the AI stays outside, you paste the brief wherever you already work.
  • Archive. Marking a tab Done archives it (title + note + date). Closing a window never deletes history. "What did I actually tie off this week?" becomes a query.
  • Set aside. Homepages (Gmail, X, YouTube), localhost dev servers with port numbers, duplicate tabs, search-results tabs (the query you typed is the intent), and video/watch pages each get their own spot in the sidebar — set aside, out of the way of real triage.
  • Asleep-tab safe. Safari nulls a suspended ("asleep") tab's URL but keeps its title. Loose Ends keys notes on the URL and the title, so a note never vanishes when Safari suspends a tab — and reattaches when it wakes.
  • Make it yours. Dark by default, with a Light/Dark toggle that isn't your system's decision to make. On top of that neutral Mac-native base, a one-click yarn-spool accent (denim · thread teal · violet · rose · terracotta). Green (safe) and amber (urgency) never change — they mean something.

Windows view: the Ready-to-close shelf — Window 4 is green because every tab is settled; Window 1 shows how far it has to go

The hard rule: the machine never closes anything in Safari except a close you clicked and confirmed. Every close is logged to logs/loose-ends.log.


Install

This is the works-today-on-Safari path — no store, no approval, no waiting. (The one-click Mac App Store version is in submission.)

Zero dependencies. Zero pip install. Python 3 (ships with macOS) and Safari — that's it.

git clone https://github.com/ssskay/loose-ends.git
cd loose-ends
python3 serve.py

Then open http://localhost:8765.

To stop it: Ctrl-C.

First run: the macOS Automation permission

The very first time, macOS will ask whether the app running python3 (usually Terminal) may control Safari. This is macOS doing its job, not a bug. If the dashboard shows a permission screen instead of your tabs:

  1. Open System Settings → Privacy & Security → Automation.
  2. Find Terminal (or whichever app you launched python3 from).
  3. Turn on the switch for Safari underneath it.
  4. Back in the dashboard, hit Refresh.

Full path, for reference:

System Settings → Privacy & Security → Automation → Terminal → Safari

Loose Ends only ever reads your tabs on a poll (on load and when you hit Refresh — no background daemon), and only closes a window when you click close and confirm.

Your data never leaves your machine

There is no account, no server-you-don't-run, no telemetry, no network calls at all. Everything lives in files next to the code:

  • Your notes, verdicts, dates, and done-log go in a local SQLite file (loose-ends.db), which is git-ignored — it is never committed and never uploaded.
  • Closes are recorded to logs/loose-ends.log, also local and git-ignored.
  • The only thing the app talks to is your own Safari, on your own Mac, over localhost.

So sharing this repo shares the code, never anyone's tabs or notes.

Sharing it with someone else

Because it's zero-dependency and self-contained, "installing" for a friend is just: send them the repo (or the clone URL), and they run the same three lines above. They get their own empty loose-ends.db on first run. No build, no packaging, no account for them either. (A one-click browser-extension version — no Python, no permission dance — is the planned next step; see below.)


How it's built

No AI. No extension. No account. No dependencies. Runs entirely on your machine.

Safari ──AppleScript (read-only poll)──▶ Python stdlib server ──▶ localhost dashboard
  ▲                                            │
  └──AppleScript close (explicit click ──── SQLite (notes, verdicts,
      + confirm, only ever user-initiated)     dates, done-log)
File Role
serve.py Thin entry point at the repo root — python3 serve.py just boots the server in app/.
index.html The whole UI. Vanilla JS + CSS, one file, no build step. It is extension/'s boot.js + dashboard.html + app.js inlined verbatim — the same dashboard the extension ships. Edit one, mirror the others; tests/test_ui_sync.py enforces it.
app/server.py The whole server. Python 3 stdlib only (http.server, sqlite3, subprocess).
app/extractor.py Read-only Safari reader via osascript. Never touches Safari state.
app/closer.py The only module that closes anything — and only on a confirmed request.
app/clustering.py Window-blind topic clustering (repeated-phrase seeds → domain fallback → Unclustered).
app/keys.py URL/title hashing and classification. The napped-tab keying lives here.
app/store.py SQLite: notes, verdicts, done-log, remembered splits.
app/applog.py One logger → logs/loose-ends.log.
app/__init__.py Package marker; REPO_ROOT so the DB, logs, and index.html still anchor to the repo root.

Safari quirks it's built around

  1. URL of tabmissing value for tabs Safari has napped. The title survives, so notes key on the title as a fallback and backfill the URL when the tab wakes.
  2. First run needs Automation permission (see above).
  3. No tab events from outside Safari, so it polls on load + Refresh. Deliberately not a background daemon — this is a triage tool, not something running in your menu bar.

Tests

python3 -m pytest        # the whole suite, or run one file directly:
python3 tests/test_keys.py         # keying survives naps in both directions
python3 tests/test_clustering.py   # the rhythm-heaven / steven-universe / ditto fixture
python3 tests/test_settled.py      # the per-verdict "safe to close" rule
python3 tests/test_payload.py      # dashboard payload, with a MOCKED extractor (never touches Safari)
python3 tests/test_ui_copy.py      # guards the visible UI strings against regressions
python3 tests/test_ui_sync.py      # the three UI copies must stay byte-identical

Tests never touch Safari — the extractor is mocked, so the suite is safe to run anywhere.


Not in v0 (on purpose)

AI/LLM anything · auto-close · background daemons · accounts/sync · notifications.

Queued for v1: semantic clustering — local, offline embeddings so "Lars Turns Pink" joins the Steven Universe cluster without sharing a word — and clustering on page content, not just titles. Both stay LLM-free at runtime. (The AI-brief export that was queued here has shipped — see Export as AI brief above.)

The browser extension has shipped (see Get it above) — no Python server, no Automation-permission dance. It lives in extension/, with the Safari/Xcode build under safari/; build-it-yourself steps are in extension/README.md. Both flavors now run the same dashboard, down to the byte — the extension's dashboard.html + app.js inlined are exactly what index.html is, and tests/test_ui_sync.py fails the build if the copies ever drift apart again.


Credit where it's due

Loose Ends was inspired by tab-out by Zara Zhang — the Chrome extension that turns your open tabs into a dashboard. It's not a fork (different browser, different language, zero shared code); it's a Safari-native cousin with a different thesis. On Chrome? Use tab-out — it's great. Loose Ends exists because Safari people deserve tools too.

License

MIT — see LICENSE. Loose Ends shares no code with tab-out; the credit above is gratitude, not a license obligation.

About

Intent-first window triage for Safari. Every open window is a loose end — note why each tab is open, give it a verdict, close the window without losing the intent.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages