5 Commits
24 changed files with 943 additions and 455 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ A deploy is a release that isn't uploaded: the same notarized disk image, instal
2. Sort the client Macs `list_clients` shows (leaving out the iPhone) by whether they run a byre server of their own: one does when `launchctl print gui/$(id -u)/run.fc.byre` exits 0 there (`client_bash`). Update the app on each one that runs no server, one Mac at a time, through the client tools and without asking first: upload `~/byre-deploy/byre.dmg` and `~/byre-deploy/install.sh` to the same paths there (`client_upload`), then run `~/byre-deploy/install.sh ~/byre-deploy/byre.dmg` there through `client_bash`. It installs, prints the build number and the commit, then quits the app if it is running and reopens it on the new build, which ends the `client_bash` call as the client disconnects while the script carries on. Wait until `list_clients` shows that Mac back on the new build and commit, and report them. Every one of these must be done before any server restarts, since a protocol change can leave an old client unable to reach a new server to be updated.
3. Only then update each client Mac that runs a server, the same way. Its server sees its executable replaced and restarts on the new build.
4. Install on the Mac you run on with `mac/install.sh ~/byre-deploy/byre.dmg`, always as the last action of the turn. The service runs `/Applications/Byre.app/Contents/Helpers/byre`; within 5 seconds it sees that file replaced and exits, and launchd starts the new server and `byre-hands`. Everything is signed with Developer ID, so the macOS privacy grants (Screen Recording, Accessibility, Automation) survive. `install.sh` relaunches its app if it is running, and leaves it closed if not.
5. When you resume, confirm it came up: read the tail of `~/Library/Logs/byre.log`, and `GET /api/tailnet` at the loopback address (`byre check` prints it as `loopback`), whose `state` must be `Running`. Report the new build number first (`plutil -extract CFBundleVersion raw /Applications/Byre.app/Contents/Info.plist` prints it; the iPhone build uploaded to TestFlight has the same one), then what you deployed and what you checked.
5. When you resume, confirm it came up: read the tail of `~/Library/Logs/byre.log`, and `GET /api/tailnet` at the loopback address (`byre check` prints it as `loopback`), whose `state` must be `Running`. Report the new build number first (`plutil -extract CFBundleVersion raw /Applications/Byre.app/Contents/Info.plist` prints it; it is the Unix time of the build, and the iPhone build uploaded to TestFlight has its own, a few minutes later), then what you deployed and what you checked.
6. End the report with release notes for everything on main since the last release tag (`git describe --tags --abbrev=0` names it), written for a byre user in the "Releasing" shape (`## New` and `## Fixed`, each a bullet with a bold short name and what it does or what now works; no `## Install`). Read `git diff <tag>..HEAD`, not just the commit subjects, and leave out changes no user sees.
Deploying means building with `mac/deploy.sh` on the Mac you run on. Never stand in for it by running `mac/package.sh` yourself, not even to build once for several Macs. Every Mac, this one included, gets the disk image `mac/deploy.sh` left in `~/byre-deploy/`.
+60 -1
View File
@@ -461,6 +461,65 @@ paths:
schema: { $ref: "#/components/schemas/Todo" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/dir:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
get:
operationId: getProjectDir
description: >
One directory on the server, directories first, then by name. Entries named .git are left out;
gitignored entries inside the project's repository checkout are included and marked.
parameters:
- { name: path, in: query, required: false, schema: { type: string }, description: "Slash-separated, absolute or relative to the project's repository root; empty or absent for the root." }
responses:
"200":
description: The directory's entries.
content:
application/json:
schema: { $ref: "#/components/schemas/DirListing" }
"400": { $ref: "#/components/responses/Error" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/file:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
get:
operationId: getProjectFile
description: >
A regular file on the server. Text is returned up to a size limit; binary files return no content.
parameters:
- { name: path, in: query, required: true, schema: { type: string }, description: "Slash-separated, absolute or relative to the project's repository root." }
responses:
"200":
description: The file.
content:
application/json:
schema: { $ref: "#/components/schemas/FileContent" }
"400": { $ref: "#/components/responses/Error" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/image:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
get:
operationId: getProjectImage
description: >
An image file on the server. path is absolute or relative to the project's repository root.
PNG, JPEG, GIF, WebP and SVG, chosen by extension.
parameters:
- { name: path, in: query, required: true, schema: { type: string } }
responses:
"200":
description: The file's bytes.
content:
image/png: { schema: { type: string, format: binary } }
image/jpeg: { schema: { type: string, format: binary } }
image/gif: { schema: { type: string, format: binary } }
image/webp: { schema: { type: string, format: binary } }
image/svg+xml: { schema: { type: string, format: binary } }
"400": { $ref: "#/components/responses/Error" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/todo/tasks:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
@@ -2375,7 +2434,7 @@ components:
host: { type: string, description: The device's name from its Byre-Host header. }
device: { type: string, description: "The kind of device the app runs on (Mac or iPhone), from its Byre-Device header." }
commit: { type: string, description: The git commit the app was built from, from its Byre-Commit header; empty when the client sent none. }
build: { type: string, description: "The app's build number (its CFBundleVersion, the Unix time of that commit), from its Byre-Build header; empty when the client sent none." }
build: { type: string, description: "The app's build number (its CFBundleVersion, the Unix time it was built), from its Byre-Build header; empty when the client sent none." }
connected_at: { type: string, format: date-time, description: When the client's WebSocket connected. }
last_action_at: { type: string, format: date-time, description: When byre last received a request the human made from this client since byre started; absent when none. }
sync: { $ref: "#/components/schemas/ClientSync" }
+1 -1
View File
@@ -8,7 +8,7 @@ Left out on the phone: the Server tab and running byre locally, the shell, file,
## Building
`mac/phone.sh` builds the app with `xcodebuild` (a Swift package can't be built for iOS by `swift build`) in the same shared directory and under the same lock as `mac/bundle.sh`, assembles `Byre.app` itself (icon through `actool`, fonts, `Info.plist` from `mac/phone/Info.plist` plus the version, commit and `DT*` keys App Store Connect checks), signs it ad hoc with `mac/phone/byre.entitlements`, and wraps it in `mac/.build/phone/iphoneos/Byre.xcarchive`. Release only, like the Mac app. `xcodebuild -exportArchive -archivePath <archive> -exportOptionsPlist mac/phone/export.plist -exportPath <dir> -allowProvisioningUpdates` re-signs it for the App Store and uploads it to TestFlight, using the Apple account Xcode is signed into: Xcode makes the distribution certificate and profile itself, so none are kept in the repo. Uploading needs the App ID `run.fc.byre.ios` (iCloud with the container `iCloud.run.fc.byre.mac`, and Push Notifications) and its app record in App Store Connect; neither can be made through Apple's API. The version is the last release tag's (App Store Connect takes only numbers) and the build number is the Mac app's (`docs/operations.md`, "Releases"). App Store Connect refuses a build number that isn't higher than every earlier upload, so a second upload of the same commit fails; a commit count wouldn't do, since it can go down when a branch with many commits uploads before its squash merge lands on main.
`mac/phone.sh` builds the app with `xcodebuild` (a Swift package can't be built for iOS by `swift build`) in the same shared directory and under the same lock as `mac/bundle.sh`, assembles `Byre.app` itself (icon through `actool`, fonts, `Info.plist` from `mac/phone/Info.plist` plus the version, commit and `DT*` keys App Store Connect checks), signs it ad hoc with `mac/phone/byre.entitlements`, and wraps it in `mac/.build/phone/iphoneos/Byre.xcarchive`. Release only, like the Mac app. `xcodebuild -exportArchive -archivePath <archive> -exportOptionsPlist mac/phone/export.plist -exportPath <dir> -allowProvisioningUpdates` re-signs it for the App Store and uploads it to TestFlight, using the Apple account Xcode is signed into: Xcode makes the distribution certificate and profile itself, so none are kept in the repo. Uploading needs the App ID `run.fc.byre.ios` (iCloud with the container `iCloud.run.fc.byre.mac`, and Push Notifications) and its app record in App Store Connect; neither can be made through Apple's API. The version is the last release tag's (App Store Connect takes only numbers) and the build number is the Unix time of the build (`docs/operations.md`, "Releases"). App Store Connect refuses a build number that isn't higher than every earlier upload, which a build time always is, so the same commit can be uploaded again; a commit count wouldn't do, since it can go down when a branch with many commits uploads before its squash merge lands on main.
iOS finds the app's App Intents (the Run workflow shortcut the Action button can run) only through `Byre.app/Metadata.appintents`. Xcode makes that bundle for app targets but not for a Swift package's executable, so `mac/phone.sh` runs `appintentsmetadataprocessor` itself, inside the build lock, on the source list and `.swiftconstvalues` the compiler leaves in the derived data. It has to read the sources from the shared copy the build compiled, which the next build of another worktree replaces. The simulator has no Shortcuts app, so an intent can only be tried on a phone.
+2 -2
View File
@@ -38,7 +38,7 @@ Each preview gets its own port on byre's node rather than a path under byre's or
## Releases
A release is one notarized disk image, `byre-<version>.dmg`, holding `Byre.app` with the server, `byre-hands` and `byre-update` in `Contents/Helpers`. Images are served from `https://dl.byre.run/`; each release's notes are on `https://g.fc.run/ai/byre/releases`. `mac/package.sh` builds one, `mac/deploy.sh` builds one for installing on the owner's Macs and keeps a copy in `~/byre-deploy/` (see `AGENTS.md`, "Deploying"), and `mac/release.sh <notes>` publishes it from a clean checkout at a pushed `v<version>` tag. The build number (`CFBundleVersion`) of both the Mac and iPhone apps is the Unix time of the commit they were built from (`git log -1 --format=%ct`), so one deploy's apps share it; the apps send it in their `Byre-Build` header and the Status tab's client list shows it. A deployed build's `CFBundleVersion` is at least the last release's, so the updater never offers to go back to it.
A release is one notarized disk image, `byre-<version>.dmg`, holding `Byre.app` with the server, `byre-hands` and `byre-update` in `Contents/Helpers`. Images are served from `https://dl.byre.run/`; each release's notes are on `https://g.fc.run/ai/byre/releases`. `mac/package.sh` builds one, `mac/deploy.sh` builds one for installing on the owner's Macs and keeps a copy in `~/byre-deploy/` (see `AGENTS.md`, "Deploying"), and `mac/release.sh <notes>` publishes it from a clean checkout at a pushed `v<version>` tag. The build number (`CFBundleVersion`) of the Mac and iPhone apps is the Unix time each was built (`date +%s`), so a deploy's iPhone build is a few minutes after its Mac build; the apps send it in their `Byre-Build` header and the Status tab's client list shows it. A deployed build's `CFBundleVersion` is above every earlier release's, so the updater never offers to go back; a release of a commit already deployed is built later, so the updater offers it to the Macs running that deploy, which takes them to the release's version name.
The appcast must never name an image that isn't available yet, and a published g.fc.run release must already have its attachments. If a failed release run leaves a draft on the releases page, delete it there before running again.
@@ -69,7 +69,7 @@ byre.run and byre.team are registered at Porkbun, with Cloudflare as their DNS,
- `https://dl.byre.run/` is the R2 bucket `byre`'s custom domain, holding what `mac/release.sh` uploads.
- byre.team, and every path on it, redirects to `https://byre.run/` through a redirect rule on its zone (Rules → Redirect Rules in the dashboard), over proxied `AAAA 100::` records for `byre.team` and `www.byre.team`.
`site/deploy.sh` and `mac/release.sh` read a Cloudflare API token from the generic password with service `byre-cloudflare` (`security add-generic-password -s byre-cloudflare -a cloudflare -w`, which prompts for it); it needs Account → Cloudflare Pages: Edit and Workers R2 Storage: Edit, and Zone → DNS: Edit, Single Redirect: Edit, Zone Settings: Edit and Cache Purge: Purge on both zones. byre.run's Browser Cache TTL is "Respect Existing Headers", so browsers keep the short cache on `byre.dmg` and `appcast.xml`.
`site/deploy.sh` and `mac/release.sh` read a Cloudflare API token from the generic password with service `byre-cloudflare` (`security add-generic-password -s byre-cloudflare -a cloudflare -w`, which prompts for it); it needs Account → Cloudflare Pages: Edit and Workers R2 Storage: Edit, and Zone → DNS: Edit, Single Redirect: Edit, Zone Settings: Edit and Cache Purge: Purge on both zones. byre.run's Browser Cache TTL is "Respect Existing Headers", so browsers keep the short cache on `byre.dmg` and revalidate `appcast.xml` (`no-cache`) on every fetch. The app checks the appcast hourly (`SUScheduledCheckInterval` in `mac/Info.plist`, Sparkle's minimum).
## Agent vendors
+22
View File
@@ -3,6 +3,7 @@ package agents
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"strings"
@@ -143,6 +144,16 @@ func (m *Manager) codexItemCompleted(ctx context.Context, l *live, s *codex.Sess
return nil
}
return m.emit(ctx, l, agentActor, "agent.thinking", map[string]any{"text": text})
case codex.ThreadItemTypeImageGeneration:
if item.SavedPath == "" || l.reviewID != "" || l.owner != "" {
return nil
}
path, err := copyToMedia(MediaDir(l.dir), string(item.SavedPath))
if err != nil {
return err
}
return m.emit(ctx, l, agentActor, "agent.message", map[string]any{"text": fmt.Sprintf("![%s](<%s>)", imageAlt(item.RevisedPrompt), path)})
}
if !codexToolItems[item.Type] {
@@ -188,6 +199,17 @@ func (m *Manager) codexItemCompleted(ctx context.Context, l *live, s *codex.Sess
return nil
}
func imageAlt(prompt string) string {
alt := strings.NewReplacer("[", "", "]", "", "\n", " ").Replace(strings.TrimSpace(prompt))
if r := []rune(alt); len(r) > 120 {
alt = string(r[:120]) + "…"
}
if alt == "" {
return "generated image"
}
return alt
}
func codexToolResult(item codex.ThreadItem) (string, bool) {
status := codexStatus(item)
switch item.Type {
+125
View File
@@ -0,0 +1,125 @@
package agents
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"g.fc.run/ai/byre/internal/codex"
"g.fc.run/ai/byre/internal/mcp"
)
const imageGenGuidance = "You make images for another agent. Call your image_gen tool exactly once with the prompt you are given, unchanged, then reply with only the word done."
func (m *Manager) generateImageTool(l *live) mcp.Tool {
return mcp.Tool{
Name: "generate_image",
Description: "Generate an image (a photo, illustration, texture, sprite, mockup) with OpenAI's image model through byre's Codex sign-in, saved as a PNG in the task's media folder. " +
"The reply gives its path: show it inline by embedding it in your final message as a Markdown image, ![what it shows](<path>), or copy it into the worktree when the project uses it. " +
"Use it whenever the human asks for a picture, rather than drawing one in SVG or HTML. It takes a minute or two.",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"prompt": {"type": "string", "description": "What to draw: subject, style, composition, lighting, and any exact text."}
},
"required": ["prompt"]
}`),
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
return m.generateImage(ctx, l, raw)
},
}
}
func (m *Manager) generateImage(ctx context.Context, l *live, raw json.RawMessage) (string, error) {
args := struct {
Prompt string `json:"prompt"`
}{}
err := json.Unmarshal(raw, &args)
if err != nil {
return "", fmt.Errorf("malformed generate_image call: %w", err)
}
if strings.TrimSpace(args.Prompt) == "" {
return "", fmt.Errorf("generate_image needs a prompt")
}
media := MediaDir(l.dir)
err = os.MkdirAll(m.codexHome, 0o700)
if err != nil {
return "", err
}
s, err := codex.Start(ctx, codex.Options{
Cwd: media,
Env: []string{"CODEX_HOME=" + m.codexHome},
Guidance: imageGenGuidance,
})
if err != nil {
return "", fmt.Errorf("start codex: %w", err)
}
defer s.Close()
_, err = s.Send(ctx, args.Prompt)
if err != nil {
return "", fmt.Errorf("start codex turn: %w", err)
}
saved := ""
for {
select {
case <-ctx.Done():
s.Kill()
return "", fmt.Errorf("no image after %s", mcp.Timeout(ctx))
case ev, ok := <-s.Events():
if !ok {
return "", fmt.Errorf("codex exited before the image was made")
}
switch ev.Method {
case codex.MethodItemCompleted:
n := codex.ItemCompletedNotification{}
err := json.Unmarshal(ev.Params, &n)
if err != nil {
return "", err
}
if n.Item.Type != codex.ThreadItemTypeImageGeneration {
continue
}
if n.Item.Failure != nil {
return "", fmt.Errorf("image generation failed: %s (limit %s)", n.Item.Failure.Type, n.Item.Failure.LimitID)
}
saved = string(n.Item.SavedPath)
case codex.MethodTurnCompleted:
n := codex.TurnCompletedNotification{}
err := json.Unmarshal(ev.Params, &n)
if err != nil {
return "", err
}
if n.Turn.Error != nil {
return "", fmt.Errorf("codex: %s", n.Turn.Error.Message)
}
if saved == "" {
return "", fmt.Errorf("codex finished without making an image")
}
path, err := copyToMedia(media, saved)
if err != nil {
return "", err
}
return fmt.Sprintf("Saved %s", path), nil
}
}
}
}
func copyToMedia(media, src string) (string, error) {
data, err := os.ReadFile(src)
if err != nil {
return "", err
}
err = os.MkdirAll(media, 0o755)
if err != nil {
return "", err
}
dest := filepath.Join(media, filepath.Base(src))
return dest, os.WriteFile(dest, data, 0o644)
}
+15
View File
@@ -0,0 +1,15 @@
How agents make real images (photos, illustrations) with generate_image, and how they show inline in the transcript.
# Generating images
`generate_image` makes a raster image from a prompt and saves it as a PNG in the task's media folder (`<worktree>-media/`, outside the worktree, deleted when the task closes). Embed the path it returns in your final message as `![what it shows](<path>)` and the human sees it inline in the transcript; copy it into the worktree when the project itself uses it.
It runs one Codex turn on the Mac byre runs on, under byre's Codex home, and calls Codex's built-in image tool (OpenAI's image model), so:
- It needs Codex signed in there (the `setup` article covers it); until then it fails with `401 Unauthorized`.
- Each image counts against the ChatGPT plan's Codex usage, whichever vendor the calling agent runs on.
- One call takes a minute or two; give it a timeout of at least 3 minutes.
Codex agents (gpt-* models) also have the image tool built in. An image one makes is copied into the media folder and shown inline in the transcript by byre, with no Markdown needed.
Write the prompt as a short spec: subject, style or medium, composition, lighting, and any exact text in quotes. For a change, generate again with the one change stated; it can't edit an earlier image in place.
+1
View File
@@ -885,6 +885,7 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
return nil, err
}
g += screenshotGuidance
tools = append(tools, m.generateImageTool(l))
hands, err := m.handsMCP(sess.Id, media)
if err != nil {
return nil, err
+514 -358
View File
@@ -882,7 +882,7 @@ type CompactCreate struct {
// ConnectedClient defines model for ConnectedClient.
type ConnectedClient struct {
// Build The app's build number (its CFBundleVersion, the Unix time of that commit), from its Byre-Build header; empty when the client sent none.
// Build The app's build number (its CFBundleVersion, the Unix time it was built), from its Byre-Build header; empty when the client sent none.
Build string `json:"build"`
// Commit The git commit the app was built from
@@ -1873,6 +1873,23 @@ type ListLeadEventsParams struct {
Turns *int `form:"turns,omitempty" json:"turns,omitempty"`
}
// GetProjectDirParams defines parameters for GetProjectDir.
type GetProjectDirParams struct {
// Path Slash-separated, absolute or relative to the project's repository root; empty or absent for the root.
Path *string `form:"path,omitempty" json:"path,omitempty"`
}
// GetProjectFileParams defines parameters for GetProjectFile.
type GetProjectFileParams struct {
// Path Slash-separated, absolute or relative to the project's repository root.
Path string `form:"path" json:"path"`
}
// GetProjectImageParams defines parameters for GetProjectImage.
type GetProjectImageParams struct {
Path string `form:"path" json:"path"`
}
// GetTaskChangesParams defines parameters for GetTaskChanges.
type GetTaskChangesParams struct {
// Since A commit to compare with instead of the merge-base, such as a user.message event's head. Not allowed with snapshot.
@@ -2170,6 +2187,15 @@ type ServerInterface interface {
// (PUT /api/projects/{projectId}/audit-workflow)
SetAuditWorkflow(w http.ResponseWriter, r *http.Request, projectId string)
// (GET /api/projects/{projectId}/dir)
GetProjectDir(w http.ResponseWriter, r *http.Request, projectId string, params GetProjectDirParams)
// (GET /api/projects/{projectId}/file)
GetProjectFile(w http.ResponseWriter, r *http.Request, projectId string, params GetProjectFileParams)
// (GET /api/projects/{projectId}/image)
GetProjectImage(w http.ResponseWriter, r *http.Request, projectId string, params GetProjectImageParams)
// (PUT /api/projects/{projectId}/review-workflow)
SetReviewWorkflow(w http.ResponseWriter, r *http.Request, projectId string)
@@ -3147,6 +3173,132 @@ func (siw *ServerInterfaceWrapper) SetAuditWorkflow(w http.ResponseWriter, r *ht
handler.ServeHTTP(w, r)
}
// GetProjectDir operation middleware
func (siw *ServerInterfaceWrapper) GetProjectDir(w http.ResponseWriter, r *http.Request) {
var err error
_ = err
// ------------- Path parameter "projectId" -------------
var projectId string
err = runtime.BindStyledParameterWithOptions("simple", "projectId", r.PathValue("projectId"), &projectId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectId", Err: err})
return
}
// Parameter object where we will unmarshal all parameters from the context
var params GetProjectDirParams
// ------------- Optional query parameter "path" -------------
err = runtime.BindQueryParameterWithOptions("form", true, false, "path", r.URL.Query(), &params.Path, runtime.BindQueryParameterOptions{Type: "string", Format: ""})
if err != nil {
var requiredError *runtime.RequiredParameterError
if errors.As(err, &requiredError) {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"})
} else {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err})
}
return
}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.GetProjectDir(w, r, projectId, params)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// GetProjectFile operation middleware
func (siw *ServerInterfaceWrapper) GetProjectFile(w http.ResponseWriter, r *http.Request) {
var err error
_ = err
// ------------- Path parameter "projectId" -------------
var projectId string
err = runtime.BindStyledParameterWithOptions("simple", "projectId", r.PathValue("projectId"), &projectId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectId", Err: err})
return
}
// Parameter object where we will unmarshal all parameters from the context
var params GetProjectFileParams
// ------------- Required query parameter "path" -------------
err = runtime.BindQueryParameterWithOptions("form", true, true, "path", r.URL.Query(), &params.Path, runtime.BindQueryParameterOptions{Type: "string", Format: ""})
if err != nil {
var requiredError *runtime.RequiredParameterError
if errors.As(err, &requiredError) {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"})
} else {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err})
}
return
}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.GetProjectFile(w, r, projectId, params)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// GetProjectImage operation middleware
func (siw *ServerInterfaceWrapper) GetProjectImage(w http.ResponseWriter, r *http.Request) {
var err error
_ = err
// ------------- Path parameter "projectId" -------------
var projectId string
err = runtime.BindStyledParameterWithOptions("simple", "projectId", r.PathValue("projectId"), &projectId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectId", Err: err})
return
}
// Parameter object where we will unmarshal all parameters from the context
var params GetProjectImageParams
// ------------- Required query parameter "path" -------------
err = runtime.BindQueryParameterWithOptions("form", true, true, "path", r.URL.Query(), &params.Path, runtime.BindQueryParameterOptions{Type: "string", Format: ""})
if err != nil {
var requiredError *runtime.RequiredParameterError
if errors.As(err, &requiredError) {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"})
} else {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err})
}
return
}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.GetProjectImage(w, r, projectId, params)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// SetReviewWorkflow operation middleware
func (siw *ServerInterfaceWrapper) SetReviewWorkflow(w http.ResponseWriter, r *http.Request) {
@@ -4790,6 +4942,9 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
m.HandleFunc(http.MethodDelete+" "+options.BaseURL+"/api/schedules/{scheduleId}", wrapper.DeleteSchedule)
m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/schedules/{scheduleId}", wrapper.UpdateSchedule)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/todo", wrapper.GetTodo)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/dir", wrapper.GetProjectDir)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/file", wrapper.GetProjectFile)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/image", wrapper.GetProjectImage)
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/projects/{projectId}/todo/tasks", wrapper.StartTodoItem)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}", wrapper.GetTask)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/workflow", wrapper.GetTaskWorkflow)
@@ -4856,363 +5011,364 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
// const string: with thousands of chunks the chained `+` fold is several
// times slower for the Go compiler than parsing a slice literal.
var swaggerSpec = []string{
"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",
}
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
+35 -9
View File
@@ -29,11 +29,24 @@ func (s *Server) GetTaskDir(w http.ResponseWriter, r *http.Request, taskID strin
if !ok {
return
}
rel := ""
if params.Path != nil {
rel = *params.Path
serveDir(w, r, t.WorktreePath, params.Path)
}
func (s *Server) GetProjectDir(w http.ResponseWriter, r *http.Request, projectID string, params api.GetProjectDirParams) {
p, err := s.store.GetProject(r.Context(), projectID)
if err != nil {
writeStoreError(w, err)
return
}
name, full := taskPath(t.WorktreePath, rel)
serveDir(w, r, p.RepoPath, params.Path)
}
func serveDir(w http.ResponseWriter, r *http.Request, root string, p *string) {
rel := ""
if p != nil {
rel = *p
}
name, full := taskPath(root, rel)
f, err := os.Open(full)
if err != nil {
@@ -84,7 +97,7 @@ func (s *Server) GetTaskDir(w http.ResponseWriter, r *http.Request, taskID strin
listing.Truncated = true
}
real, err := realDir(t.WorktreePath, full)
real, err := realDir(root, full)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
@@ -97,7 +110,7 @@ func (s *Server) GetTaskDir(w http.ResponseWriter, r *http.Request, taskID strin
for _, e := range listing.Entries {
paths = append(paths, path.Join(real, e.Name))
}
ignored, err := git.Ignored(r.Context(), t.WorktreePath, paths)
ignored, err := git.Ignored(r.Context(), root, paths)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
@@ -154,11 +167,24 @@ func (s *Server) GetTaskFile(w http.ResponseWriter, r *http.Request, taskID stri
if !ok {
return
}
if params.Path == "" {
serveFile(w, t.WorktreePath, params.Path)
}
func (s *Server) GetProjectFile(w http.ResponseWriter, r *http.Request, projectID string, params api.GetProjectFileParams) {
p, err := s.store.GetProject(r.Context(), projectID)
if err != nil {
writeStoreError(w, err)
return
}
serveFile(w, p.RepoPath, params.Path)
}
func serveFile(w http.ResponseWriter, root, rel string) {
if rel == "" {
writeError(w, http.StatusBadRequest, errors.New("path is required"))
return
}
name, full := taskPath(t.WorktreePath, params.Path)
name, full := taskPath(root, rel)
fi, err := os.Stat(full)
if err != nil {
@@ -166,7 +192,7 @@ func (s *Server) GetTaskFile(w http.ResponseWriter, r *http.Request, taskID stri
return
}
if !fi.Mode().IsRegular() {
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a regular file", params.Path))
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a regular file", rel))
return
}
+17 -5
View File
@@ -26,14 +26,26 @@ func (s *Server) GetTaskImage(w http.ResponseWriter, r *http.Request, taskID str
if !ok {
return
}
serveImage(w, r, t.WorktreePath, params.Path)
}
path := params.Path
func (s *Server) GetProjectImage(w http.ResponseWriter, r *http.Request, projectID string, params api.GetProjectImageParams) {
p, err := s.store.GetProject(r.Context(), projectID)
if err != nil {
writeStoreError(w, err)
return
}
serveImage(w, r, p.RepoPath, params.Path)
}
func serveImage(w http.ResponseWriter, r *http.Request, root, rel string) {
path := rel
if !filepath.IsAbs(path) {
path = filepath.Join(t.WorktreePath, path)
path = filepath.Join(root, path)
}
real, err := filepath.EvalSymlinks(path)
if errors.Is(err, fs.ErrNotExist) {
writeError(w, http.StatusNotFound, fmt.Errorf("%s does not exist", params.Path))
writeError(w, http.StatusNotFound, fmt.Errorf("%s does not exist", rel))
return
}
if err != nil {
@@ -43,7 +55,7 @@ func (s *Server) GetTaskImage(w http.ResponseWriter, r *http.Request, taskID str
contentType := worktreeImageTypes[strings.ToLower(filepath.Ext(real))]
if contentType == "" {
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a PNG, JPEG, GIF, WebP or SVG file", params.Path))
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a PNG, JPEG, GIF, WebP or SVG file", rel))
return
}
@@ -60,7 +72,7 @@ func (s *Server) GetTaskImage(w http.ResponseWriter, r *http.Request, taskID str
return
}
if !info.Mode().IsRegular() {
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a file", params.Path))
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a file", rel))
return
}
+2
View File
@@ -24,6 +24,8 @@
<true/>
<key>SUAutomaticallyUpdate</key>
<true/>
<key>SUScheduledCheckInterval</key>
<integer>3600</integer>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>
+5 -1
View File
@@ -38,6 +38,10 @@ enum LeftTab: String {
case lead, status, notes, workflows, server, settings
}
enum ProjectTab {
case tasks, files, settings
}
@MainActor
@Observable
final class Overlays {
@@ -53,7 +57,7 @@ final class Overlays {
var leadFocus = 0
var tabFocus = 0
var projectFocus = false
var projectSettings = false
var projectTab = ProjectTab.tasks
var taskList = false
@ObservationIgnored var pickerFrame: CGRect = .zero
@ObservationIgnored var menuHover = false
-9
View File
@@ -139,15 +139,6 @@ final class Drafts {
func prune(_ store: Store) {
var changed = false
for key in Set(composerTexts.keys).union(composerImages.keys).union(lostImages.keys) {
let taskId = key.hasPrefix("message:") ? String(key.dropFirst("message:".count)) : key
if taskId != "lead" && store.findTask(taskId)?.status != .active {
composerTexts[key] = nil
composerImages[key] = nil
lostImages[key] = nil
changed = true
}
}
for taskId in comments.keys where store.findTask(taskId)?.status != .active {
comments[taskId] = nil
changed = true
+77 -32
View File
@@ -5,19 +5,31 @@ typealias DirListing = Components.Schemas.DirListing
typealias DirEntry = Components.Schemas.DirEntry
typealias FileContent = Components.Schemas.FileContent
enum FileRoot: Hashable {
case task(String)
case project(String)
var name: String {
if case .project = self {
return "repo"
}
return "worktree"
}
}
struct FilePlace: Equatable {
var dir = ""
var file: String?
}
private struct FilesKey: Equatable {
let taskId: String?
let root: FileRoot?
let place: FilePlace
}
extension Store {
var filePlace: FilePlace {
selectedTaskId.flatMap { filePlaces[$0] } ?? FilePlace()
func filePlace(_ root: FileRoot?) -> FilePlace {
root.flatMap { filePlaces[$0] } ?? FilePlace()
}
func openFile(_ taskId: String, _ path: String) {
@@ -34,13 +46,22 @@ extension Store {
}
func showInFiles(_ taskId: String, _ path: String) {
if path.isEmpty || path.hasSuffix("/") {
filePlaces[taskId] = FilePlace(dir: path == "/" ? path : String(path.dropLast()))
} else {
filePlaces[taskId] = FilePlace(dir: parent(path), file: path)
}
filePlaces[.task(taskId)] = filePlace(at: path)
route("#/task/\(taskId)/files")
}
func showInProjectFiles(_ projectId: String, _ path: String, _ overlays: Overlays) {
filePlaces[.project(projectId)] = filePlace(at: path)
overlays.projectTab = .files
route("#/project/\(projectId)")
}
private func filePlace(at path: String) -> FilePlace {
if path.isEmpty || path.hasSuffix("/") {
return FilePlace(dir: path == "/" ? path : String(path.dropLast()))
}
return FilePlace(dir: parent(path), file: path)
}
}
private func join(_ dir: String, _ name: String) -> String {
@@ -78,6 +99,7 @@ private func shown(_ c: FileContent) -> Int64 {
@Observable
private final class FilesModel {
let store: Store
var root: FileRoot?
private(set) var listing: DirListing?
private(set) var content: FileContent?
private(set) var error = ""
@@ -91,14 +113,14 @@ private final class FilesModel {
}
var place: FilePlace {
store.filePlace
store.filePlace(root)
}
func go(_ p: FilePlace) {
guard let taskId = store.selectedTaskId else {
guard let root else {
return
}
store.filePlaces[taskId] = p
store.filePlaces[root] = p
}
func reset() {
@@ -114,22 +136,29 @@ private final class FilesModel {
}
func load() async {
guard let taskId = store.selectedTaskId else {
guard let root else {
return
}
target += 1
let started = target
let p = place
let client = store.api.client
do {
if let file = p.file {
let fresh = try await store.api.client.getTaskFile(path: .init(taskId: taskId), query: .init(path: file)).ok.body.json
let fresh = switch root {
case .task(let id): try await client.getTaskFile(path: .init(taskId: id), query: .init(path: file)).ok.body.json
case .project(let id): try await client.getProjectFile(path: .init(projectId: id), query: .init(path: file)).ok.body.json
}
if started == target {
content = fresh
listing = nil
loads += 1
}
} else {
let fresh = try await store.api.client.getTaskDir(path: .init(taskId: taskId), query: .init(path: p.dir)).ok.body.json
let fresh = switch root {
case .task(let id): try await client.getTaskDir(path: .init(taskId: id), query: .init(path: p.dir)).ok.body.json
case .project(let id): try await client.getProjectDir(path: .init(projectId: id), query: .init(path: p.dir)).ok.body.json
}
if started == target {
listing = fresh
content = nil
@@ -147,10 +176,7 @@ private final class FilesModel {
}
}
func transcript(_ fresh: [ByreEvent]) {
if !fresh.contains(where: { $0.kind == "agent.tool_result" || $0.kind == "turn.ended" }) {
return
}
func changed() {
timer?.cancel()
timer = Task {
try? await Task.sleep(for: .milliseconds(1500))
@@ -176,11 +202,12 @@ private final class FilesModel {
if let l = listing {
return "\(l.entries.count)\(l.truncated ? "+" : "") \(l.entries.count == 1 ? "entry" : "entries")"
}
return store.selectedTaskId != nil ? "Loading…" : ""
return root != nil ? "Loading…" : ""
}
}
struct FilesView: View {
let root: FileRoot?
@Environment(Store.self) private var store
@ViewState private var model: FilesModel?
@@ -192,12 +219,26 @@ struct FilesView: View {
}
}
.frame(maxHeight: .infinity, alignment: .top)
.task(id: FilesKey(taskId: store.selectedTaskId, place: store.filePlace)) {
.task(id: FilesKey(root: root, place: store.filePlace(root))) {
let m = model ?? FilesModel(store)
model = m
m.root = root
m.reset()
let off = store.selectedFeed?.appended.add { [weak m] events in
m?.transcript(events)
let off: (() -> Void)? = switch root {
case .task:
store.selectedFeed?.appended.add { [weak m] events in
if events.contains(where: { $0.kind == "agent.tool_result" || $0.kind == "turn.ended" }) {
m?.changed()
}
}
case .project(let id):
store.events.add { [weak m] ev in
if (ev.kind == "task.merged" || ev.kind == "task.merged_to_main") && ev.projectId == id {
m?.changed()
}
}
case nil:
nil
}
await untilCancelled()
off?()
@@ -217,7 +258,7 @@ private struct FilesHead: View {
let base = absolute ? "/" : ""
HStack(alignment: .firstTextBaseline, spacing: 12) {
FlowLayout(spacing: 2) {
crumb(absolute ? "/" : "worktree", base, parts.isEmpty)
crumb(absolute ? "/" : model.root?.name ?? "worktree", base, parts.isEmpty)
ForEach(Array(parts.enumerated()), id: \.offset) { i, part in
if !absolute || i > 0 {
Text("/").foregroundStyle(Palette.muted)
@@ -255,18 +296,18 @@ private struct FilesBody: View {
@Environment(Store.self) private var store
var body: some View {
if store.selectedTaskId == nil {
if model.root == nil {
EmptyText(text: "No task selected.")
} else if !model.error.isEmpty {
EmptyText(text: model.error, error: true)
} else if let c = model.content, let taskId = store.selectedTaskId {
} else if let c = model.content, let root = model.root {
if let file = model.place.file, isImage(file) {
FileImage(taskId: taskId, path: file, load: model.loads)
FileImage(root: root, path: file, load: model.loads)
.id(model.generation)
} else if c.binary {
EmptyText(text: "Binary file.")
} else if model.place.file?.lowercased().hasSuffix(".md") == true {
FileMarkdown(content: c, taskId: taskId, dir: model.place.dir)
FileMarkdown(content: c, root: root, dir: model.place.dir)
.id(model.generation)
} else {
FileText(content: c)
@@ -285,7 +326,7 @@ private struct FilesBody: View {
EntryRow(label: e._type == .dir ? "\(e.name)/" : e.name, kind: e._type, ignored: e.ignored, meta: meta, symlink: e.symlink) {
if e._type == .dir {
model.go(FilePlace(dir: join(l.path, e.name)))
} else if e._type == .file && isPage(e.name), let taskId = store.selectedTaskId {
} else if e._type == .file && isPage(e.name), case .task(let taskId) = model.root {
store.openPage(.file(taskId: taskId, path: join(l.path, e.name)))
} else if e._type == .file {
model.go(FilePlace(dir: l.path, file: join(l.path, e.name)))
@@ -355,13 +396,17 @@ private struct EntryRow: View {
private struct FileMarkdown: View {
let content: FileContent
let taskId: String?
let root: FileRoot
let dir: String
var body: some View {
let ctx = switch root {
case .task(let id): MDContext(taskId: id, dir: dir)
case .project(let id): MDContext(project: id, dir: dir)
}
ScrollView {
VStack(alignment: .leading, spacing: 0) {
MarkdownView(blocks: parseMarkdown(content.text), ctx: MDContext(taskId: taskId, dir: dir))
MarkdownView(blocks: parseMarkdown(content.text), ctx: ctx)
if content.truncated {
Text("Showing the first \(bytes(shown(content))) of \(bytes(content.size)).")
.foregroundStyle(Palette.muted)
@@ -376,7 +421,7 @@ private struct FileMarkdown: View {
}
private struct FileImage: View {
let taskId: String
let root: FileRoot
let path: String
let load: Int
@Environment(Store.self) private var store
@@ -407,7 +452,7 @@ private struct FileImage: View {
}
.task(id: load) {
do {
let data = try await ImageCache.fetch(.worktree(taskId: taskId, path: path), store.api)
let data = try await ImageCache.fetch(.file(root, path: path), store.api)
guard let img = PlatformImage(data: data) else {
image = nil
error = "Not a readable image."
+10 -5
View File
@@ -3,7 +3,7 @@ import SwiftUI
enum ImageSource: Hashable {
case blob(String)
case worktree(taskId: String, path: String)
case file(FileRoot, path: String)
case data(Data)
}
@@ -11,7 +11,7 @@ private let dataImage = Re(#"^data:image/(png|jpeg|gif|webp);"#, caseless: true)
private let fileURL = Re(#"^file://"#, caseless: true)
private let anyScheme = Re(#"^[a-z][a-z0-9+.-]*:"#, caseless: true)
func worktreeImageSource(_ path: String, _ taskId: String?, dir: String = "") -> ImageSource? {
func worktreeImageSource(_ path: String, _ root: FileRoot?, dir: String = "") -> ImageSource? {
if dataImage.test(path) {
guard let comma = path.firstIndex(of: ","), path[..<comma].lowercased().hasSuffix(";base64"), let data = Data(base64Encoded: String(path[path.index(after: comma)...])) else {
return nil
@@ -29,10 +29,10 @@ func worktreeImageSource(_ path: String, _ taskId: String?, dir: String = "") ->
} else if !dir.isEmpty && !file.hasPrefix("/") {
file = "\(dir)/\(file)"
}
guard let taskId else {
guard let root else {
return nil
}
return .worktree(taskId: taskId, path: file)
return .file(root, path: file)
}
@MainActor
@@ -76,11 +76,16 @@ final class ImageCache {
case .png(let b), .jpeg(let b), .imageGif(let b), .imageWebp(let b): b
}
return try await Data(collecting: body, upTo: 32 << 20)
case .worktree(let taskId, let path):
case .file(.task(let taskId), let path):
let body = switch try await api.client.getTaskImage(path: .init(taskId: taskId), query: .init(path: path)).ok.body {
case .png(let b), .jpeg(let b), .imageGif(let b), .imageWebp(let b), .imageSvgXml(let b): b
}
return try await Data(collecting: body, upTo: 32 << 20)
case .file(.project(let projectId), let path):
let body = switch try await api.client.getProjectImage(path: .init(projectId: projectId), query: .init(path: path)).ok.body {
case .png(let b), .jpeg(let b), .imageGif(let b), .imageWebp(let b), .imageSvgXml(let b): b
}
return try await Data(collecting: body, upTo: 32 << 20)
}
}
}
+31 -13
View File
@@ -18,11 +18,14 @@ enum AppLink {
return c.url!
}
static func file(_ taskId: String, _ path: String) -> URL {
static func file(_ root: FileRoot, _ path: String) -> URL {
var c = URLComponents()
c.scheme = "byre-app"
c.host = "file"
c.queryItems = [URLQueryItem(name: "task", value: taskId), URLQueryItem(name: "path", value: path)]
c.queryItems = switch root {
case .task(let id): [URLQueryItem(name: "task", value: id), URLQueryItem(name: "path", value: path)]
case .project(let id): [URLQueryItem(name: "project", value: id), URLQueryItem(name: "path", value: path)]
}
return c.url!
}
@@ -61,7 +64,11 @@ func openAppLink(_ url: URL, _ store: Store, _ overlays: Overlays) -> OpenURLAct
case "route":
store.route(q["hash"] ?? "")
case "file":
store.openFile(q["task"] ?? "", q["path"] ?? "")
if let project = q["project"] {
store.showInProjectFiles(project, q["path"] ?? "", overlays)
} else {
store.openFile(q["task"] ?? "", q["path"] ?? "")
}
case "preview":
let taskId = q["task"] ?? ""
store.route("#/task/\(taskId)")
@@ -169,17 +176,23 @@ struct InlineRenderer {
let store: Store
let size: CGFloat
let taskId: String?
let project: String?
let weight: CGFloat
let dir: String
private let root: String
init(store: Store, size: CGFloat, taskId: String?, weight: CGFloat = 400, dir: String = "") {
init(store: Store, size: CGFloat, taskId: String?, project: String? = nil, weight: CGFloat = 400, dir: String = "") {
self.store = store
self.size = size
self.taskId = taskId
self.project = project
self.weight = weight
self.dir = dir
root = taskId.flatMap { store.findTask($0)?.worktreePath } ?? ""
if let taskId {
root = store.findTask(taskId)?.worktreePath ?? ""
} else {
root = project.flatMap { id in store.projects.first { $0.id == id }?.repoPath } ?? ""
}
}
private struct Style {
@@ -275,7 +288,7 @@ struct InlineRenderer {
s.italic = true
render(inner, s, &out)
case .image(let src, let alt):
if let source = worktreeImageSource(src, taskId, dir: dir) {
if let source = worktreeImageSource(src, files, dir: dir) {
out.append(.image(source, src: src))
} else {
link(src, [.text(alt.isEmpty ? src : alt)], style, &out)
@@ -327,11 +340,15 @@ struct InlineRenderer {
render(inner, style, &out)
}
private var files: FileRoot? {
taskId.map(FileRoot.task) ?? project.map(FileRoot.project)
}
private func fileLink(_ path: String) -> URL? {
guard let taskId, !root.isEmpty, let p = normalPath(path) else {
guard let files, !root.isEmpty, let p = normalPath(path) else {
return nil
}
return AppLink.file(taskId, p)
return AppLink.file(files, p)
}
private func codeFile(_ code: String) -> URL? {
@@ -451,6 +468,7 @@ struct MDContext {
var size: CGFloat = 13
var color: Color = Palette.text
var taskId: String?
var project: String?
var choice: ((MDItem, Bool) -> AnyView)?
var discuss: ((MDItem) -> AnyView)?
var gridColumns = 4
@@ -590,7 +608,7 @@ private struct FlowBuilder {
}
private func inline(_ inlines: [MDInline], _ size: CGFloat) -> NSAttributedString {
let a = InlineRenderer(store: store, size: size, taskId: ctx.taskId, weight: ctx.weight, dir: ctx.dir).text(inlines)
let a = InlineRenderer(store: store, size: size, taskId: ctx.taskId, project: ctx.project, weight: ctx.weight, dir: ctx.dir).text(inlines)
return nativeString(a)
}
@@ -902,7 +920,7 @@ struct InlineView: View {
@Environment(\.centerImages) private var centerImages
var body: some View {
let segs = InlineRenderer(store: store, size: ctx.size, taskId: ctx.taskId, weight: ctx.weight, dir: ctx.dir).segments(inlines)
let segs = InlineRenderer(store: store, size: ctx.size, taskId: ctx.taskId, project: ctx.project, weight: ctx.weight, dir: ctx.dir).segments(inlines)
VStack(alignment: .leading, spacing: 4) {
ForEach(Array(segs.enumerated()), id: \.offset) { _, seg in
switch seg {
@@ -948,7 +966,7 @@ private struct BlockView: View {
case .paragraph(let inlines):
InlineView(inlines: inlines, ctx: ctx)
case .heading(let level, let inlines):
InlineView(inlines: [.strong(inlines)], ctx: MDContext(size: level == 3 ? 14 : 13, color: ctx.color, taskId: ctx.taskId, dir: ctx.dir))
InlineView(inlines: [.strong(inlines)], ctx: MDContext(size: level == 3 ? 14 : 13, color: ctx.color, taskId: ctx.taskId, project: ctx.project, dir: ctx.dir))
.padding(.top, 4)
case .rule:
Rectangle()
@@ -956,7 +974,7 @@ private struct BlockView: View {
.frame(height: 1)
.padding(.vertical, 2)
case .quote(let blocks):
MarkdownView(blocks: blocks, ctx: MDContext(size: ctx.size, color: Palette.muted, taskId: ctx.taskId, dir: ctx.dir))
MarkdownView(blocks: blocks, ctx: MDContext(size: ctx.size, color: Palette.muted, taskId: ctx.taskId, project: ctx.project, dir: ctx.dir))
.padding(.leading, 13)
.overlay(alignment: .leading) {
Rectangle().fill(Palette.border).frame(width: 3)
@@ -1107,7 +1125,7 @@ private struct DetailsBlock: View {
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(open ? "▾" : "▸")
.foregroundStyle(Palette.accent)
InlineView(inlines: summary, ctx: MDContext(size: ctx.size, color: Palette.accent, taskId: ctx.taskId, dir: ctx.dir))
InlineView(inlines: summary, ctx: MDContext(size: ctx.size, color: Palette.accent, taskId: ctx.taskId, project: ctx.project, dir: ctx.dir))
}
.contentShape(Rectangle())
}
+1 -1
View File
@@ -244,7 +244,7 @@ struct TaskTabBody: View {
case .changes:
ChangesView()
case .files:
FilesView()
FilesView(root: store.selectedTaskId.map(FileRoot.task))
case .page:
if let shown = store.selectedTaskId.flatMap({ store.shownPages[$0] }) {
PageTabs(shown: shown)
+19 -12
View File
@@ -17,11 +17,14 @@ struct ProjectView: View {
VStack(spacing: 0) {
if store.focusedProject() != nil {
HStack(spacing: 4) {
PaneTabButton(label: "Tasks", active: !overlays.projectSettings) {
overlays.projectSettings = false
PaneTabButton(label: "Tasks", active: overlays.projectTab == .tasks) {
overlays.projectTab = .tasks
}
PaneTabButton(label: "Settings", active: overlays.projectSettings) {
overlays.projectSettings = true
PaneTabButton(label: "Files", active: overlays.projectTab == .files) {
overlays.projectTab = .files
}
PaneTabButton(label: "Settings", active: overlays.projectTab == .settings) {
overlays.projectTab = .settings
}
Spacer(minLength: 0)
}
@@ -30,14 +33,18 @@ struct ProjectView: View {
Rectangle().fill(Palette.border).frame(height: 1)
}
}
ScrollView {
if let p = store.focusedProject() {
ProjectBlock(project: p, todo: todos[p.id], settings: overlays.projectSettings)
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 16)
} else {
Muted(text: "No project selected.")
if let p = store.focusedProject(), overlays.projectTab == .files {
FilesView(root: .project(p.id))
} else {
ScrollView {
if let p = store.focusedProject() {
ProjectBlock(project: p, todo: todos[p.id], settings: overlays.projectTab == .settings)
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 16)
} else {
Muted(text: "No project selected.")
}
}
}
}
+1 -1
View File
@@ -525,7 +525,7 @@ private struct Attachments: View {
case .image:
let src = (a.image?.isEmpty == false ? a.image : a.path) ?? ""
let caption = a.caption ?? ""
if let source = worktreeImageSource(src, taskId) {
if let source = worktreeImageSource(src, .task(taskId)) {
ByreImage(source: source, src: src)
} else if !src.isEmpty {
InlineView(inlines: [.link(href: src, [.text(caption.isEmpty ? src : caption)])], ctx: MDContext(taskId: taskId))
+1 -1
View File
@@ -83,7 +83,7 @@ final class Store {
private(set) var leadMore = false
var todoBusy: [String: String] = [:]
var pickedMembers: [String: String] = [:]
var filePlaces: [String: FilePlace] = [:]
var filePlaces: [FileRoot: FilePlace] = [:]
private(set) var drafts: Drafts?
let images: ImageDrafts
let imageCache = ImageCache()
+1 -1
View File
@@ -49,7 +49,7 @@ ditto "$build/TailscaleKit.framework" "$app/Contents/Frameworks/TailscaleKit.fra
cp "$mac/Info.plist" "$app/Contents/Info.plist"
plutil -insert ByreCommit -string "$(git -C "$root" rev-parse HEAD)" "$app/Contents/Info.plist"
plutil -insert CFBundleShortVersionString -string "$version" "$app/Contents/Info.plist"
plutil -insert CFBundleVersion -string "$(git -C "$root" log -1 --format=%ct)" "$app/Contents/Info.plist"
plutil -insert CFBundleVersion -string "$(date +%s)" "$app/Contents/Info.plist"
cp "$mac"/fonts/*.woff2 "$app/Contents/Resources/fonts/"
cp "$icns" "$app/Contents/Resources/byre.icns"
cp "$mac/byre.provisionprofile" "$app/Contents/embedded.provisionprofile"
+1 -1
View File
@@ -45,7 +45,7 @@ plutil -insert CFBundleIcons -xml "$(plutil -extract CFBundleIcons xml1 -o - "$b
plutil -insert ByreCommit -string "$(git -C "$root" rev-parse HEAD)" "$plist"
version=$(git -C "$root" describe --tags --abbrev=0)
plutil -insert CFBundleShortVersionString -string "${version#v}" "$plist"
plutil -insert CFBundleVersion -string "$(git -C "$root" log -1 --format=%ct)" "$plist"
plutil -insert CFBundleVersion -string "$(date +%s)" "$plist"
platform=$(xcrun --sdk "$sdk" --show-sdk-platform-version)
sdkbuild=$(xcrun --sdk "$sdk" --show-sdk-build-version)
plutil -insert CFBundleSupportedPlatforms -xml "<array><string>$(if [ "$sdk" = iphoneos ]; then echo iPhoneOS; else echo iPhoneSimulator; fi)</string></array>" "$plist"
+1 -1
View File
@@ -32,7 +32,7 @@ put() {
put "$dmg" "$dmg" application/x-apple-diskimage "public, max-age=31536000, immutable"
put "$dmg.sha256" "$dmg.sha256" text/plain "public, max-age=31536000, immutable"
put byre.dmg "$dmg" application/x-apple-diskimage "public, max-age=300"
put appcast.xml appcast.xml application/xml "public, max-age=300"
put appcast.xml appcast.xml application/xml "no-cache"
curl -fsS -o /dev/null -X POST -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" --data '{"files":["https://dl.byre.run/byre.dmg","https://dl.byre.run/appcast.xml"]}' https://api.cloudflare.com/client/v4/zones/b53a5f8cb60313ba6789aba3559cb4b8/purge_cache
request=$out/release.json