Save. Sync. Copy in a tap. Stash is a snippet manager for iPhone, iPad, and Mac: capture text, links, code, addresses, and images, keep them in sync with iCloud, and grab them again the moment you need them.
- Features
- Screenshots
- Tech Stack
- How It Works
- Getting Started
- Architecture
- CloudKit and sync
- Deployment
- Development
- Project Highlights
- Contributing
- License
- Smart tiles — Stash classifies what you save: text, code, link, address, and image, and treats each type appropriately (open in Safari, drop a pin, copy formatted code, and more).
- Share extension — Save from any app that supports the system share sheet without breaking your flow.
- Fast copy — Tap a tile to copy with haptics and confirmation; edits are safe with draft handling on Apple platforms.
- iCloud sync — One stash across iPhone, iPad, and Mac; data stays in the user’s iCloud account.
- Menu bar on Mac — Recent items, paste from clipboard, image import, and screenshot capture without a Dock icon.
- Marketing site — React + Vite landing and legal pages with build-time pre-rendering: production
npm run buildemits static HTML for/,/privacy, and/terms(hydrates to the same SPA in the browser).
- Native Apple stack — SwiftUI surfaces, shared StashCore library, CloudKit sync with CKSyncEngine.
- Modern web tooling — React 19, Vite 8, SCSS modules, ESLint + Prettier, Phosphor icons.
flowchart TB
User[User]
iOS[Stash iOS / iPadOS]
MacMB[StashMenuBar macOS]
ShareExt[StashShareExtension]
Core[StashCore]
CK[iCloud / CloudKit]
Web[Marketing site - Vite React]
User --> iOS
User --> MacMB
User --> ShareExt
iOS --> Core
MacMB --> Core
ShareExt --> Core
Core -->|CKSyncEngine| CK
User -->|Reads marketing / legal| Web
- Swift & SwiftUI — Primary UI for phone, tablet, and menu bar.
- StashCore — Shared models, content classification, CloudKit sync (
StashCloudSync, tile records). - CloudKit — Private database sync; custom zone support via sync engine.
- Share extension — System share sheet target.
- Push (menu bar) — Used with sync where configured; see CloudKit deployment notes.
- Pre-rendering —
vite buildproduces a client bundle plus an SSR bundle;scripts/prerender.mjsrunsrender()fromentry-serverfor each public route and writes fully populated<div id="root">…</div>into staticindex.htmlfiles (SEO-friendly, fast first paint). - React 19 — Landing, feature sections, privacy, and terms.
- Vite 8 — Dev server and production builds.
- React Router 7 — Client routing; server render path used only at build time for those prerendered pages.
- SCSS — Global tokens, section styling, component modules.
- Phosphor Icons — Iconography on the site.
- User captures content (type, paste, photo, share sheet, clipboard, or screenshot on Mac).
- Stash classifies the payload into a tile kind (text, code, URL, address, image).
- Local storage + sync persist the tile; CloudKit propagates changes to the user’s other devices.
- User recalls the item from the main app or the Mac menu bar—copy, open link, directions, or edit.
sequenceDiagram
participant DeviceA as Device A
participant Core as StashCore
participant CK as CloudKit
participant DeviceB as Device B
DeviceA->>Core: Create / update tile
Core->>CK: Upload record (CKSyncEngine)
CK->>DeviceB: Fetch / push notification path
DeviceB->>Core: Merge remote changes
Core->>DeviceB: Update UI
- For
web/: Node.js 20+ recommended (matches current Vite/React toolchain). - For
ios/: macOS with Xcode (recent stable), Apple Developer account for signing, iCloud capability, and (for menu bar push) correct entitlements and provisioning as described in the CloudKit doc.
git clone https://github.com/areknow/stash.git
cd stash/web
npm install
npm run devOpen the URL Vite prints (typically http://localhost:5173) to view the marketing site locally.
cd web
npm run buildOutput is under web/dist/ (client assets plus pre-rendered HTML for /, /privacy, /terms). Use npm run preview to smoke-test the production build.
- Open
ios/Stash.xcodeprojin Xcode. - Select the Stash (iOS), StashMenuBar (macOS), or extension scheme as needed.
- Choose a simulator or device and run (⌘R).
Brand assets (icons, splash, logos) live in each target’s Assets.xcassets and are maintained per target.
stash/
├── web/ # Marketing site (Vite + React)
│ ├── src/
│ │ ├── pages/ # Marketing, Privacy, Terms
│ │ ├── components/ # Layout + marketing sections
│ │ └── assets/screenshots/
│ ├── scripts/prerender.mjs
│ └── package.json
├── ios/
│ ├── Stash.xcodeproj
│ ├── Stash/ # Main iOS / iPadOS app
│ ├── StashMenuBar/ # macOS menu bar companion
│ ├── StashShareExtension/
│ ├── StashCore/ # Shared logic + CloudKit sync
│ └── CLOUDKIT_DEPLOYMENT.md
└── README.md
Why CloudKit?
- First-party sync with no custom backend to operate.
- Data stays private to the user’s Apple ID.
- Fits a personal productivity app that already targets Apple platforms.
Why StashCore?
- One implementation of tile models, classification, and sync for app, menu bar, and extension.
- Reduces drift between targets when behavior changes.
There is no public HTTP API: the source of truth for user data is CloudKit (private database), coordinated by StashCore.
Before a production release, deploy the CloudKit schema from development to production and verify record types (for example StashTileRecord and fields such as kindRaw, title, textBody, codeBody, imageURLString, imageAsset, timestamps). Full checklist, migration notes, and menu bar push/sync troubleshooting live in ios/CLOUDKIT_DEPLOYMENT.md.
Ship the Stash app (and menu bar product if distributed with it or separately per your App Store setup) through Xcode and App Store Connect. Complete CloudKit schema deployment before the first production release that uses new record definitions.
The site is a static export that already includes pre-rendered HTML for /, /privacy, and /terms from npm run build (not a Node server at runtime). Deploy the contents of web/dist/ to any static host (object storage + CDN, Netlify, Cloudflare Pages, etc.). Configure the host so:
/privacyand/termsresolve to their pre-renderedindex.htmlfiles under those paths (as produced by the build), and- client-side routing fallback is consistent with your host’s SPA rules if you add more routes later.
The marketing site does not require secrets for local dev. If you add analytics or API calls later, document keys in a local .env pattern and exclude them from git.
cd web
# Dev server
npm run dev
# Production build + prerender
npm run build
# Typecheck & lint
npm run typecheck
npm run lint
# Format
npm run format
npm run format:check- Use Xcode schemes and the standard build/test workflow.
- For sync or push oddities after aggressive debugging, see the defaults reset and entitlement notes in
ios/CLOUDKIT_DEPLOYMENT.md.
- Reliable CloudKit sync — Custom CKSyncEngine-based pipeline with careful lifecycle (single sync controller in menu bar process, schema migration from older containers).
- Smart content typing — Heuristics and shared core logic so tiles behave correctly across UI surfaces (badges, actions, copy semantics).
- Menu bar + full app — Shared core, different UX constraints (popover, recents, clipboard, screenshots) without duplicating sync.
- Marketing performance — Vite builds plus route-level SSR prerender for key URLs.
This is primarily a personal / product repository. Ideas and bug reports are welcome via issues; for substantial changes, open a discussion first so direction stays aligned with the App Store app and CloudKit constraints.
Private. All rights reserved.




