Skip to content

feat(mac-client): Lisa.app native Mac client for the chat GUI - #26

Merged
oratis merged 1 commit into
mainfrom
feat/mac-client-25
May 28, 2026
Merged

feat(mac-client): Lisa.app native Mac client for the chat GUI#26
oratis merged 1 commit into
mainfrom
feat/mac-client-25

Conversation

@oratis

@oratis oratis commented May 27, 2026

Copy link
Copy Markdown
Owner

Closes #25. Sibling to LisaIsland.app — same WKWebView wrapper pattern, but for the full chat GUI rather than the always-on-top pill.

Why a separate app

LisaIsland.app Lisa.app (this PR)
What it is Passive observer Active conversation
Dock icon ✓ (mascot)
Window chrome borderless, transparent standard (titlebar, traffic lights)
Z-order always on top normal; ⌘Tab works
Loads `/island` `/` (chat GUI)
When to use Always running in background Open when you want to chat
Activation policy `.accessory` `.regular`

Install one, both, or neither. Both just hit `localhost:5757`; neither knows about the other.

What's in this PR

`packaging/mac-client/` — a SwiftPM target that produces `Lisa.app`:

```
Sources/Lisa/
├── main.swift 19 LOC NSApplication boot (.regular policy)
├── AppDelegate.swift 175 LOC window lifecycle + standard menu bar
├── MainWindow.swift 55 LOC NSWindow + frame autosave
└── WebContent.swift 94 LOC WKWebView + auto-retry + external links


### App icon generated at build time

\`build.sh\` runs \`sips\` + \`iconutil\` against the existing
\`src/web/assets/lisa-mascot.png\` (1024×1024) to produce the
\`.iconset\` then \`.icns\`. No binary blobs committed; the icon stays
in sync if the mascot changes.

### Standard menu bar

| Shortcut | Action |
|---|---|
| ⌘N | New / show window |
| ⌘W | Close window (app stays in Dock) |
| ⌘R | Reload chat |
| ⌘F | Toggle fullscreen |
| ⌘M | Minimize |
| ⌘H | Hide |
| ⌘Q | Quit |

### Frame autosave

NSWindow's \`frameAutosaveName\` is set to \`ai.meetlisa.app.MainWindow\`,
so window position + size persist across launches. Defaults to 1200×800
centered on first run.

### External links

\`target=_blank\` links inside the chat (e.g. someone shares a URL)
open in the user's default browser via \`NSWorkspace.shared.open\`
rather than spawning a new in-app WebView.

## Verified locally

✓ \`bash build.sh\` succeeds; produces universal binary (arm64 + x86_64)
✓ \`Lisa.app/Contents/Resources/AppIcon.icns\` generated (~1 MB)
✓ App launches; mascot icon appears in Dock
✓ Two ESTABLISHED connections to localhost:5757 (page + \`/events\` SSE)
✓ \`⌘W\` closes window; \`⌘Q\` quits; Dock click reopens after \`⌘W\`
✓ \`⌘R\` reloads
✓ Runs simultaneously with LisaIsland.app — both reach LISA independently

## Verification

\`\`\`sh
lisa serve --web
cd packaging/mac-client
bash build.sh
open Lisa.app
\`\`\`

## Out of scope

- Multi-window — single window for now
- URL scheme integration with LisaIsland (\`open_full_gui\` could deep-link to Lisa.app instead of opening Safari) — separate small PR if/when desired
- Native menu commands talking to LISA's REST endpoints — separate small PRs (\`⌘N\` → \`POST /api/birth\` for new soul, etc.)
- Apple Developer ID signing + notarization + Homebrew Cask — Phase 4 of MAC_ISLAND_PLAN

## Diff stat

| File | LOC |
|---|---|
| \`Sources/Lisa/*.swift\` | 343 |
| \`Package.swift\` | 13 |
| \`Resources/Info.plist\` | 45 |
| \`build.sh\` | 109 |
| \`README.md\` | 101 |
| \`.gitignore\` | 14 |
| **Total** | **625** |

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Sibling to LisaIsland.app (PR #24). Same WKWebView wrapper pattern but
for the full chat experience rather than the passive pill.

Differences from LisaIsland.app:
- regular activation policy (Dock icon visible)
- standard window chrome (titlebar, traffic lights, resizable, fullscreen)
- loads localhost:5757/ instead of /island
- frame autosave (remembers position + size across launches)
- full Mac menu bar (App/File/Edit/View/Window) with standard ⌘N/W/R/F/Q
- external links open in user's default browser (NSWorkspace.shared.open)
- custom user agent "Lisa-MacClient/0.1" so the LISA server can log it

What this PR delivers:

- packaging/mac-client/Package.swift
    SwiftPM target, macOS 13+, executable.

- packaging/mac-client/Sources/Lisa/
    main.swift          — NSApplication.regular activation policy
    AppDelegate.swift   — window lifecycle + standard menu bar; closing
                          window keeps app alive (standard Mac model),
                          ⌘Q quits, Dock click reopens
    MainWindow.swift    — NSWindow, 1200×800 default, 600×500 min, frame
                          autosave under "ai.meetlisa.app.MainWindow"
    WebContent.swift    — WKWebView + auto-retry on load failure +
                          external links → NSWorkspace.shared.open

- packaging/mac-client/Resources/Info.plist
    LSUIElement: false (dock icon visible), CFBundleIconFile: AppIcon,
    bundle ID ai.meetlisa.app, macOS 13 minimum, ATS local exception.

- packaging/mac-client/build.sh
    Same swift-build + ad-hoc-sign pattern as island-mac, but ALSO
    generates Lisa.app/Contents/Resources/AppIcon.icns at build time
    from src/web/assets/lisa-mascot.png (the existing 1024×1024 mascot)
    via sips + iconutil. No binary blobs committed; icon stays in sync
    if the mascot changes.

- packaging/mac-client/README.md
    Side-by-side comparison table with LisaIsland.app, build + install
    instructions, keyboard shortcut table.

- packaging/mac-client/.gitignore
    Excludes .build/, Lisa.app/, Xcode artifacts.

Verified locally:
- Universal binary builds (`bash build.sh`)
- App launches with mascot icon in Dock
- Two ESTABLISHED connections to localhost:5757 (page + /events SSE)
- Window frame autosave works across relaunches
- ⌘W closes window, app stays in Dock
- Dock click reopens window
- ⌘R reloads chat
- Runs simultaneously alongside LisaIsland.app — both apps reach LISA
  independently, neither knows about the other

Closes #25

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oratis
oratis merged commit 7072abe into main May 28, 2026
@oratis
oratis deleted the feat/mac-client-25 branch May 28, 2026 04:44
oratis added a commit that referenced this pull request Aug 4, 2026
L7 中间层在强制设置下**真的绑定到词了**,而且比 post-norm 干净得多:

  臂                   训练acc   F1      F2      F3
  base                  —     0.585   0.540   0.525
  L1 定义进上下文          —     0.855   0.905   0.995
  post-norm 本词        0.974   0.770   0.535   0.595
  post-norm 换相反词             0.350   0.515   0.450
  ★ L7 本词            1.000   0.925   0.545   0.670
  ★ L7 换相反词                 0.070   0.410   0.290

  换词落差:L7 −0.855(0.925→0.070,不是失效,是答案跟着词整个翻过去)
           post-norm −0.420 · P9b 未强制时仅 −0.018

🔴🔴 但跨读出方向随之消失:L7 的 F2 0.545 / F3 0.670,base 是 0.540 / 0.525;
    而 P9 **未绑定**时是 0.935 / 0.945。
    ⟹ **P9 那个漂亮的跨格式迁移,正是"没绑定到词"换来的**——
       未绑定时记忆给**物体表征**打了个与词无关的标,任何读出格式都能读到。

★ 四格全测下来,从来没有一格同时拿到两者:
                          绑定到词        跨读出方向
  post-norm + 欠定(P5–P8)  ❌ 0.017       ❌
  L7 + 欠定(P9)            ❌ 0.018       ✅ 0.940
  post-norm + 强制(P10)    ◐ −0.420      ❌ 0.565
  L7 + 强制(P10)           ✅ −0.855      ❌ 0.608
  L1 上下文注入             ✅ 构造上必然    ✅ 0.905/0.995
  ⟹ 参数化路线最多只拿到其一;上下文注入两者都有。**该路线目前最强的负面证据。**

补的两处方法学漏洞:
· base/L1 参照臂(主脚本初版漏了)——逐用户 base 的 F1 从 0.240 到 0.880 都有,
  个别用户像"F3=0.080"这种极端值大半来自基座,不可单独解读。新增 p10_ref_arms.py。
· 两臂优化量不对等:900 步是为 post-norm 定的(它 ep900 才收敛),
  而 L7 在 ep150 就 loss 0.000 ⟹ 过训 6 倍,逐用户 held-out F1 方差极大(含一个 0.48)。
  早停版未测,已写进限制。

同步:REPORT 顶部警告 + §3.13 + 自我推翻 #26 + §0.4 两条新的"差点犯的错";
Method 草稿 SCOPE 补充 + §6(0-b) + 禁令 18/19。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mac client — Lisa.app (dockable native app hosting the full chat GUI)

1 participant