Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9cea997
chore: add @mymehq/sdk dependency
aicayzer May 16, 2026
4b07aa8
feat(myme): register superwhisper.recording + superwhisper.session types
aicayzer May 16, 2026
5585bb6
feat(myme): settings card + IPC scaffold for Myme integration
aicayzer May 16, 2026
e7d88fb
feat(myme): wire end-to-end auth via safeStorage-backed API key
aicayzer May 16, 2026
b65e672
feat(myme): sync engine — projection, state, diff-driven upserts
aicayzer May 17, 2026
1f037b3
feat(myme): wire reindex hook for watcher-driven incremental sync
aicayzer May 17, 2026
c727a15
feat(myme): session derivation + push pass
aicayzer May 17, 2026
dda010f
feat(myme): smoke harness for end-to-end engine verification
aicayzer May 17, 2026
bdbfb6b
feat(myme): cancel + 'push N most recent' testing knob on the card
aicayzer May 17, 2026
79914c7
feat(myme): T-165 — promote sync cap from testing knob to first-class…
aicayzer May 17, 2026
17107b6
feat(myme): T-164 — OAuth device-flow auth alongside API-key fallback…
aicayzer May 17, 2026
c911c3f
fix(myme): T-164 — narrow device-flow scopes to the server allowlist …
aicayzer May 17, 2026
e7f835e
fix(myme): T-164 — add edge.parent-of scopes to device-flow grant (#60)
aicayzer May 17, 2026
bef2705
fix(myme): T-172 — correct session edge literal from 'core.parent-of'…
aicayzer May 17, 2026
c714aaa
feat(sw-app): parameterise projection on a mapping config (#62)
aicayzer May 17, 2026
4b15afb
feat(sw-app): type-mapping picker UI in the connected card (#63)
aicayzer May 17, 2026
7858a9b
feat(sw-app): connection panel + sync controls polish (#64)
aicayzer May 17, 2026
f9af0b9
feat(sw-app): redesign the connected MymeCard layout (#65)
aicayzer May 17, 2026
c01ad18
chore(local): isolate myme install build from v0.2.x release
aicayzer May 17, 2026
723cdfc
chore(local): stub updater IPC handlers in the myme install build
aicayzer May 17, 2026
114d11e
feat(myme): rename properties.device → input_device, populate item.de…
aicayzer May 18, 2026
19d82ea
feat(myme): migration + version-aware registration + empty-content fi…
aicayzer May 18, 2026
0edbb8b
feat(settings): redesign — five tabs, grouped fillers, pipeline cards
aicayzer May 19, 2026
e1d518a
feat(settings): align switches, indent chips, segment session gap
aicayzer May 19, 2026
373f603
feat(sync): finish sync redesign — toast system + status counts + tiles
aicayzer May 20, 2026
a8edebf
chore: bump @mymehq/sdk to 5.9.0 (T-218 async bulk_action)
aicayzer May 20, 2026
e44b99f
chore: rename Myme to Marfa wholesale
aicayzer May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,30 @@ pnpm test # vitest
- IPC contract lives in `src/preload/api.ts` — every renderer call goes
through `window.api.*`. Add a channel there + a matching handler in
`src/main/ipc.ts`.

## Toasts

System-level toasts run through `sonner`, wrapped behind
`@renderer/lib/toast`. ESLint blocks direct imports of `sonner`
outside the wrapper + `components/ui/sonner.tsx`.

- **Use `toastError`** for genuine errors a user can't recover from
inline — background sync failures, unexpected API rejections, OAuth
refresh blowing up. Pass `copyText` whenever the user might need to
share details with us; the toast renders a Copy logs action when
present.
- **Use `toastInfo`** sparingly. Only for transient confirmations
with no inline home (e.g. "Purged X recordings"). Routine
sign-in / sync success is _not_ a toast — the card itself carries
that signal.
- **Never duplicate an inline error state.** The ConnectionCard
already surfaces "Last sync failed — <reason>" with a Retry
button; the toast is at most an _additional_ one-shot signal on
the transition. Fire it from a state-change `useEffect` keyed on
the error string, never on every render.
- **Never on routine success paths.** Sign-in / sync completion
both transition the card — no toast.

The single transition watcher lives in `App.tsx` (`lastToastedErrorRef`)
and observes `useMarfaStore().status` for `disconnected.lastError` /
`connected.lastError` changes.
10 changes: 4 additions & 6 deletions electron-builder.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
appId: me.cyzr.superwhisper-analytics
productName: SuperWhisper Analytics
appId: me.cyzr.superwhisper-analytics-marfa
productName: SuperWhisper Analytics (Marfa)
directories:
buildResources: build
files:
Expand Down Expand Up @@ -30,7 +30,5 @@ mac:
dmg:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
provider: github
owner: aicayzer
repo: superwhisper-analytics
# publish stanza intentionally removed for the local Marfa install build —
# no latest-mac.yml is produced and auto-update is disabled in main.ts.
10 changes: 9 additions & 1 deletion electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ const sharedAlias = { '@shared': resolve('src/shared') }

export default defineConfig({
main: {
resolve: { alias: sharedAlias }
resolve: { alias: sharedAlias },
build: {
// electron-vite's default `externalizeDepsPlugin` marks every
// entry in package.json `dependencies` as a runtime CJS require.
// The Marfa SDK is ESM-only — `require('@withmarfa/sdk')` blows up
// with `ERR_PACKAGE_PATH_NOT_EXPORTED`. Exclude the two packages
// here so they're bundled into the main process output instead.
externalizeDeps: { exclude: ['@withmarfa/sdk', '@withmarfa/shared'] }
}
},
preload: {
resolve: { alias: sharedAlias }
Expand Down
22 changes: 22 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,28 @@ export default defineConfig(
'react/prop-types': 'off'
}
},
// Funnel every `sonner` consumer through `@renderer/lib/toast`. The
// wrapper and the Toaster component are the only files allowed to
// import sonner directly; everywhere else uses `toastError` /
// `toastInfo` so behaviour stays centralised.
{
files: ['src/renderer/**/*.{ts,tsx}'],
ignores: ['src/renderer/src/lib/toast.ts', 'src/renderer/src/components/ui/sonner.tsx'],
rules: {
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'sonner',
message:
'Import `toastError` / `toastInfo` from `@renderer/lib/toast` instead — the wrapper is the only sonner consumer.'
}
]
}
]
}
},
// Recharts wrappers — TS prop types are sufficient; the plugin can't see
// them through Recharts' content-render-prop call signatures.
{
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"dependencies": {
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0",
"@withmarfa/sdk": "^1.0.0",
"@radix-ui/react-slot": "^1.2.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand All @@ -62,6 +63,7 @@
"react-day-picker": "^10.0.0",
"react-router-dom": "^7.1.5",
"recharts": "^3.2.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.6.0",
"zustand": "^5.0.13"
},
Expand Down
38 changes: 38 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions scripts/marfa-register-types.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* One-shot script: register the Superwhisper integration's custom types
* against a Marfa tenant.
*
* Reads admin credentials from `~/.marfa/admin.json` (the
* machine-local admin token used for direct-API operations), registers
* `superwhisper.recording` and `superwhisper.session`, then verifies
* each by reading back via `types.get`.
*
* Idempotent — already-registered types resolve as an upsert in Marfa,
* not a 409. Re-run safely.
*
* Not part of the runtime integration; not bundled into the app. The
* production shape (integration code registering its own types on first
* connect-after-OAuth) is out of scope for the worktree experiment.
*
* Run with:
* pnpm dlx tsx scripts/marfa-register-types.ts
*/

import { readFileSync } from 'fs'
import { homedir } from 'os'
import { join } from 'path'
import { MarfaClient, MarfaError } from '@withmarfa/sdk'
import { ALL_SCHEMAS } from '../src/main/marfa/schemas'

interface AdminCreds {
url: string
key: string
label?: string
}

function loadAdminCreds(): AdminCreds {
const path = join(homedir(), '.marfa', 'admin.json')
const raw = readFileSync(path, 'utf-8')
const parsed = JSON.parse(raw) as Partial<AdminCreds>
if (typeof parsed.url !== 'string' || typeof parsed.key !== 'string') {
throw new Error(`~/.marfa/admin.json missing url or key fields`)
}
return { url: parsed.url, key: parsed.key, label: parsed.label }
}

async function main(): Promise<void> {
const creds = loadAdminCreds()
console.log(`[register] using ${creds.label ?? 'admin'} @ ${creds.url}`)

const client = new MarfaClient({ url: creds.url, apiKey: creds.key })

for (const schema of ALL_SCHEMAS) {
try {
const registered = await client.types.register(schema)
console.log(`[register] ${registered.id}: registered (version ${registered.version})`)
} catch (err) {
if (err instanceof MarfaError) {
console.error(`[register] ${schema.id}: ${err.message}`)
throw err
}
throw err
}

// Read back to verify.
const fetched = await client.types.get(schema.id)
const fieldKeys = Object.keys(fetched.fields).sort().join(', ')
console.log(`[register] ${fetched.id}: verified — fields = ${fieldKeys}`)
}

console.log('[register] done')
}

main().catch((err: unknown) => {
console.error(err)
process.exit(1)
})
Loading