Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c09cf40
fix(mac): do not style the wrong disk when a volume named cix is mounted
dvcdsys Aug 10, 2026
d9aa0c9
feat(mac): copy generated passwords from the dialog, and say less to …
dvcdsys Aug 10, 2026
0e6a4b3
Merge pull request #232 from dvcdsys/fix/dmg-layout-volume-collision
dvcdsys Aug 10, 2026
71a735e
Merge pull request #233 from dvcdsys/fix/launcher-dialog-copy
dvcdsys Aug 10, 2026
f9f6fa3
fix(mac): notice a deleted database, retire the bootstrap password, r…
dvcdsys Aug 10, 2026
0795df7
Merge pull request #234 from dvcdsys/fix/firstrun-and-busy-state
dvcdsys Aug 10, 2026
3bee6ed
fix(mac): make Start say why it failed, and copy the password without…
dvcdsys Aug 10, 2026
450a17f
Merge pull request #235 from dvcdsys/fix/start-without-database
dvcdsys Aug 10, 2026
1c03ed3
fix(mac): route the server's no-admin refusal back to the setup wizard
dvcdsys Aug 10, 2026
75f5977
feat(mac): replace the NSMenu with a custom menu bar panel
dvcdsys Aug 10, 2026
5acda90
fix(mac): keep the panel open on Start/Stop and unify the busy loader
dvcdsys Aug 10, 2026
266b23c
feat(mac): move every dialog inside the panel, Docker-Desktop-style
dvcdsys Aug 10, 2026
46188c9
feat(mac): float dialogs over the panel content instead of replacing it
dvcdsys Aug 10, 2026
2eeaff6
fix(mac): show a busy card during the update check instead of blinking
dvcdsys Aug 10, 2026
b7dfe73
feat(mac): make the app's own version and self-update visible
dvcdsys Aug 10, 2026
df0e58e
refactor(mac): pin what the up-to-date dialog says on a released build
dvcdsys Aug 10, 2026
cfd78ee
Merge pull request #236 from dvcdsys/fix/first-run-empty-db
dvcdsys Aug 10, 2026
9e451ab
Merge pull request #237 from dvcdsys/feat/menubar-panel
dvcdsys Aug 10, 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
17 changes: 10 additions & 7 deletions .github/workflows/release-mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,21 @@ jobs:
That is the same build the Docker images are cut from, and it
updates on its own schedule: a new server does not need a new app.

### First launch will be blocked — this is expected
### macOS will block it twice — this is expected

cix is open source and is **not** signed with a paid Apple Developer
certificate, so macOS refuses the first launch with "cix cannot be
verified".
certificate, so it is not notarized. macOS refuses both the disk
image and the app inside it with "Apple could not verify…". Nothing
is wrong with the download — choose **Done**, never *Move to Bin*.

To allow it: **System Settings → Privacy & Security**, scroll to
**Security**, then click **Open Anyway** next to the message about cix.
Clear each one the same way: **System Settings → Privacy & Security**,
scroll to **Security**, then **Open Anyway** next to the message.
Once for the `.dmg` when you open it, and once for **cix.app** the
first time you launch it — the app inherits the quarantine flag from
the image, so clearing the first does not clear the second.

On macOS 15 and later the old right-click → Open shortcut no longer
works — you have to use System Settings. You only need to do this
once per installed version.
works for either. You do this once per installed version.

### Verify the download

Expand Down
2 changes: 0 additions & 2 deletions cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/dvcdsys/code-index/cli
go 1.25.12

require (
fyne.io/systray v1.12.2
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
Expand Down Expand Up @@ -34,7 +33,6 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/jsonschema-go v0.4.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
Expand Down
4 changes: 0 additions & 4 deletions cli/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA=
fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
Expand Down Expand Up @@ -43,8 +41,6 @@ github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJ
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
Expand Down
54 changes: 27 additions & 27 deletions cli/launcher/dialog_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (
"time"
)

// osascript is the dialog mechanism for the whole launcher.
// osascript dialogs — the FALLBACK layer.
//
// The menu-bar library (Phase 2) has no dialog API, and pulling in a second GUI
// toolkit to draw three alerts would double the bundle for no gain. AppleScript
// alerts are native, need no linkage, and survive the app being LSUIElement.
// The app's dialogs live inside the panel now (paneldialog_darwin.go and
// panel.html's #dialog): alert/confirm/ask/prompt/alertWithSecret there route
// to the webview once the AppKit side is up. What remains here is the same
// primitives over osascript, used only in the window before the panel exists —
// a translocated bundle refusing to run, a version query gone wrong — where a
// native modal is the only surface available.
//
// Two rules, both load-bearing:
// - Every string that reaches AppleScript goes through quoteAS. Text here is
Expand All @@ -36,20 +39,17 @@ func quoteAS(s string) string {
return strings.Join(parts, " & return & ")
}

// dialogIcon is the POSIX path to the icon dialogs are drawn with. Set once at
// startup from the bundle; empty when the launcher runs outside a .app.
// dialogIcon is the POSIX path to the icon osascript dialogs are drawn with.
// Set once at startup from the bundle; empty when running outside a .app.
var dialogIcon string

// alert shows a modal informational dialog and blocks until it is dismissed.
// osaAlert shows a modal informational dialog and blocks until dismissed.
//
// `display dialog` rather than the more obvious `display alert`, for one
// reason: an alert is drawn with the icon of the process that ran the script,
// which here is osascript — so the app's own dialogs came up wearing a generic
// folder icon. `display dialog` takes an explicit icon. The cost is that the
// title is a window title instead of bold body text; the icon is worth more.
// It is also the primitive Phase 2 needs anyway, since only `display dialog`
// supports `default answer` for text input.
func alert(title, message string) error {
// folder icon. `display dialog` takes an explicit icon.
func osaAlert(title, message string) error {
var script string
if dialogIcon != "" {
script = fmt.Sprintf(
Expand All @@ -65,6 +65,17 @@ func alert(title, message string) error {
return runOsascript(2*time.Minute, script)
}

// copyToClipboard pipes a value to pbcopy.
//
// Not AppleScript's `set the clipboard to`: that would put the secret into a
// script string, where it is one quoting mistake away from being interpreted.
// A pipe carries bytes and nothing else.
func copyToClipboard(s string) error {
cmd := exec.Command("pbcopy")
cmd.Stdin = strings.NewReader(s)
return cmd.Run()
}

// errCancelled is returned when the user dismissed a dialog instead of
// answering it. osascript reports this as exit status 1 — the same status as a
// real failure — so it has to be told apart from the error text.
Expand All @@ -81,8 +92,8 @@ func isUserCancelled(stderr string) bool {
return strings.Contains(stderr, "(-128)")
}

// prompt asks for one line of text. Returns errCancelled if the user cancels.
func prompt(title, message, defaultAnswer string) (string, error) {
// osaPrompt asks for one line of text. Returns errCancelled on cancel.
func osaPrompt(title, message, defaultAnswer string) (string, error) {
script := fmt.Sprintf(
`display dialog %s with title %s default answer %s %s buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel"`,
quoteAS(message), quoteAS(title), quoteAS(defaultAnswer), iconClause(),
Expand All @@ -101,20 +112,9 @@ func prompt(title, message, defaultAnswer string) (string, error) {
return strings.TrimSpace(answer), nil
}

// confirm shows a two-button question. Returns false when the user declines.
func confirm(title, message, okLabel string) (bool, error) {
return ask(title, message, okLabel, "Cancel")
}

// ask shows a two-button question with both labels spelled out.
//
// Separate from confirm because not every choice has a "cancel" side. "Take
// Over" versus "Leave It Alone" are two real options, and labelling the second
// one Cancel would imply it does nothing — when in fact it decides how the app
// behaves from then on.
//
// osaAsk shows a two-button question with both labels spelled out.
// yesLabel is the default button. Dismissing the dialog counts as no.
func ask(title, message, yesLabel, noLabel string) (bool, error) {
func osaAsk(title, message, yesLabel, noLabel string) (bool, error) {
script := fmt.Sprintf(
`display dialog %s with title %s %s buttons {%s, %s} default button %s cancel button %s`,
quoteAS(message), quoteAS(title), iconClause(),
Expand Down
107 changes: 0 additions & 107 deletions cli/launcher/dots_darwin.go

This file was deleted.

8 changes: 8 additions & 0 deletions cli/launcher/env_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,11 @@ func dashboardURL(vars map[string]string) string {
func localBaseURL(vars map[string]string) string {
return fmt.Sprintf("http://localhost:%d", serverPort(vars))
}

// setDefault fills a key only when it has no value yet, so re-running setup
// keeps whatever the user has chosen since the first time.
func setDefault(vars map[string]string, key, value string) {
if strings.TrimSpace(vars[key]) == "" {
vars[key] = value
}
}
Loading