Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 24 additions & 2 deletions .github/workflows/build-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,36 @@ jobs:
name: frontend-dist
path: frontend/dist

# The icon, version strings, and DPI manifest reach a Windows binary only
# through a .syso the linker finds inside the package being built. This
# workflow calls `go build` directly rather than going through
# build/windows/Taskfile.yml, so the step has to be repeated here -- doing
# it only in the Taskfile would leave every released .exe on the default
# executable icon while local builds looked correct.
- name: Compile Windows resources
if: matrix.target.platform == 'windows'
shell: bash
working-directory: build
run: |
go run github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.4 generate syso \
-icon windows/icon.ico \
-manifest windows/wails.exe.manifest \
-info windows/info.json \
-arch "$GOARCH" \
-out "../cmd/oneagent-desktop/rsrc_windows_${GOARCH}.syso"

- name: Build binaries
shell: bash
run: |
mkdir -p bin
version_ldflag="-X github.com/MaimoryLab/OneAgent/internal/version.Version=$RELEASE_VERSION"
if [ "${{ matrix.target.platform }}" = windows ]; then
GOARCH=amd64 go run github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.4 generate icons -input build/appicon.png -macfilename "" -windowsfilename build/windows/icon.ico
GOARCH=amd64 go run github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.4 generate syso -arch "${GOARCH}" -icon build/windows/icon.ico -manifest build/windows/wails.exe.manifest -out "cmd/oneagent-desktop/wails_windows_${GOARCH}.syso"
# No `generate icons` step: build/windows/icon.ico is tracked. That
# generator scales every entry from appicon.png, and its 16x16 -- the
# size Explorer and the taskbar use -- collapses into a solid block,
# so the tracked file carries a hand-drawn entry there. Regenerating
# would have replaced it on every release build.
GOARCH=amd64 go run github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.4 generate syso -arch "${GOARCH}" -icon build/windows/icon.ico -manifest build/windows/wails.exe.manifest -info build/windows/info.json -out "cmd/oneagent-desktop/wails_windows_${GOARCH}.syso"
fi
go build -tags wails,production -trimpath -buildvcs=false -ldflags="${{ matrix.target.desktop_ldflags }} $version_ldflag" -o "bin/oneagent-desktop${{ matrix.target.suffix }}" ./cmd/oneagent-desktop
rm -f "cmd/oneagent-desktop/wails_windows_${GOARCH}.syso"
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ site/

# 对竞品的定向调研笔记:含内部评价措辞,保留本机参考但不随开源仓库分发。
docs/internal/cc-switch-reference-notes.md

# Windows 资源产物:图标、版本号和 DPI manifest 由 build/windows/ 下的源文件
# 在构建时编译进 .syso。它必须落在被构建的包目录里链接器才认,构建完即删除。
cmd/oneagent-desktop/*.syso
11 changes: 11 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ Every mark is distributed as a single-colour glyph painted with
fill="currentColor", so it takes the surrounding text colour rather than
carrying a brand colour.

OneAgent's own mark is not part of that inventory and is not third-party
material. It is MaimoryLab's work, distributed under this repository's Apache-2.0
license, and appears at frontend/src/components/icons/assets/oneagent-mark.svg
in the interface and at build/appicon.png, build/darwin/icons.icns, and
build/windows/icon.ico as the application icon. It is recorded under
`firstPartyAssets` in frontend/src/components/icons/asset-rights.json, kept
separate from the `assets` inventory above because it has no third-party source,
licence text or copyright owner to record -- listing it there would assert a
provenance it does not have. It is named here so the inventory above is not read
as covering every image in the release.

Sponsor marks are a separate case and are not nominative use. The PPIO and
Novita logos under docs/assets/sponsors/ are reproduced with each owner's
permission to identify a sponsor of this project, and they do carry their brand
Expand Down
21 changes: 8 additions & 13 deletions build/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,14 @@ tasks:
cmds:
- '{{.WAILS3}} generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename ""'

generate:windows:icon:
summary: Generate the Windows application icon
run: once
dir: build
sources:
- appicon.png
generates:
- windows/icon.ico
env:
# The generator runs on the host; GOARCH may be the cross-compile target.
GOARCH: amd64
cmds:
- '{{.WAILS3}} generate icons -input appicon.png -macfilename "" -windowsfilename windows/icon.ico'
# windows/icon.ico is tracked rather than generated, so there is deliberately no
# generate:windows:icon task. `wails3 generate icons` scales every entry from
# appicon.png, and at 16x16 -- the size Explorer and the taskbar use -- the ring
# stroke, its counter, and the inner form each land on under 2.4px and blend
# into a solid block, losing the break in the ring. That entry is hand-drawn.
# A task that regenerated the file would discard it on every `task package`,
# silently and without failing. Re-run `wails3 generate icons` by hand only if
# the mark itself changes, then restore the 16x16 entry.

dev:frontend:
summary: Runs the Vite dev server (used by `task dev`)
Expand Down
Binary file modified build/appicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified build/darwin/icons.icns
Binary file not shown.
14 changes: 10 additions & 4 deletions build/windows/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ tasks:
ARCH: '{{.ARCH | default ARCH}}'
cmds:
- go build {{.BUILD_FLAGS}} -o "{{.OUTPUT}}" ./cmd/oneagent-desktop
# The .syso is a build input for GOOS=windows only. Left in place it would
# be picked up by a native Windows build of any other target in this repo,
# and its arch suffix makes it wrong for the other architecture.
- cmd: powershell Remove-item "cmd/oneagent-desktop/wails_windows_{{.ARCH | default ARCH}}.syso"
platforms: [windows]
- cmd: rm -f "cmd/oneagent-desktop/wails_windows_{{.ARCH | default ARCH}}.syso"
Expand All @@ -32,10 +35,12 @@ tasks:
generate:syso:
summary: Generates Windows resources with the application icon
dir: '{{.ROOT_DIR}}'
deps:
- task: common:generate:windows:icon
# No common:generate:windows:icon dep: build/windows/icon.ico is tracked, and
# regenerating it from appicon.png would replace its hand-drawn 16x16 entry --
# the size Explorer and the taskbar use, where a scaled copy of the mark
# collapses into a solid block.
cmds:
- '{{.WAILS3}} generate syso -arch {{.ARCH | default ARCH}} -icon build/windows/icon.ico -manifest build/windows/wails.exe.manifest -out cmd/oneagent-desktop/wails_windows_{{.ARCH | default ARCH}}.syso'
- '{{.WAILS3}} generate syso -arch {{.ARCH | default ARCH}} -icon build/windows/icon.ico -manifest build/windows/wails.exe.manifest -info build/windows/info.json -out cmd/oneagent-desktop/wails_windows_{{.ARCH | default ARCH}}.syso'
env:
# The generator runs on the host; GOARCH may be the cross-compile target.
GOARCH: amd64
Expand All @@ -48,7 +53,8 @@ tasks:
vars:
ARCH: '{{.ARCH}}'
VERSION: '{{.VERSION}}'
- task: common:generate:windows:icon
# nsis/project.nsi reads the tracked ../icon.ico directly through MUI_ICON,
# so the installer needs no icon-generation step of its own.
preconditions:
- sh: command -v makensis >/dev/null 2>&1
msg: "makensis not found. Install NSIS first."
Expand Down
Binary file modified build/windows/icon.ico
Binary file not shown.
14 changes: 14 additions & 0 deletions build/windows/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"fixed": {
"file_version": "0.3.0"
},
"info": {
"0000": {
"ProductVersion": "0.3.0",
"CompanyName": "MaimoryLab",
"FileDescription": "Local AI development environment activator",
"LegalCopyright": "(c) 2026 MaimoryLab",
"ProductName": "OneAgent"
}
}
}
8 changes: 6 additions & 2 deletions frontend/src/components/NavigationSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Boxes, FolderCog, Gauge, Layers3, Settings } from "lucide-react";
import { FolderCog, Gauge, Layers3, Settings } from "lucide-react";
import { NavLink } from "react-router-dom";

import { type TranslationKey, useI18n } from "../i18n";
import { BrandMark } from "./icons/BrandMark";
import { TaskCenter } from "./TaskCenter";

// Only real destinations belong here. /setup/* are wizard steps behind
Expand All @@ -18,8 +19,11 @@ export function NavigationSidebar() {
return (
<aside className="navigation-sidebar">
<div className="brand-lockup">
{/* The product's own mark. This was lucide's generic <Boxes> glyph --
a stock icon from the icon library standing in for a brand that did
not exist yet. */}
<span className="brand-mark" aria-hidden="true">
<Boxes size={21} strokeWidth={1.8} />
<BrandMark size={22} />
</span>
<div>
<strong>OneAgent</strong>
Expand Down
47 changes: 47 additions & 0 deletions frontend/src/components/icons/BrandMark.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { render } from "@testing-library/react";
import { describe, expect, it } from "vitest";

import { AGENT_ICON_IDS, agentMarkKind } from "./agents";
import { BrandMark } from "./BrandMark";

describe("BrandMark", () => {
it("renders an inline svg so currentColor resolves against this document", () => {
const { container } = render(<BrandMark />);
const svg = container.querySelector("svg");
expect(svg).toBeTruthy();
// An <img src> would make the SVG a separate document where currentColor
// falls back to black; the Agent marks are inlined for the same reason.
expect(container.querySelector("img")).toBeNull();
expect(svg?.getAttribute("fill")).toBe("currentColor");
expect(svg?.querySelectorAll("path").length).toBeGreaterThan(0);
});

it("does not hardcode the brand blue, so it follows the theme", () => {
const { container } = render(<BrandMark />);
const markup = container.innerHTML.toLowerCase();
// #007AFF equals --blue on the light theme but not the dark one (#0a84ff),
// so a literal here would be visibly wrong in dark mode.
expect(markup).not.toContain("007aff");
expect(markup).not.toContain("0a84ff");
});

it("is hidden from assistive technology, since the wordmark carries the name", () => {
const { container } = render(<BrandMark />);
expect(container.firstElementChild?.getAttribute("aria-hidden")).toBe("true");
});

it("honours the requested size", () => {
const { container } = render(<BrandMark size={40} />);
const host = container.firstElementChild as HTMLElement;
expect(host.style.width).toBe("40px");
expect(host.style.height).toBe("40px");
});

it("stays out of the third-party Agent mark inventory", () => {
// The MARKS table exists to track vendor artwork with a recorded licence and
// SHA-256. This mark is first-party, so registering it there would assert a
// third-party provenance it does not have.
expect(AGENT_ICON_IDS).not.toContain("oneagent");
expect(agentMarkKind("oneagent")).toBe("fallback");
});
});
34 changes: 34 additions & 0 deletions frontend/src/components/icons/BrandMark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* OneAgent's own mark.
*
* Deliberately not part of the MARKS table in agents.tsx. That table exists to
* track third-party vendor artwork -- every entry carries a source, licence, and
* SHA-256 in asset-rights.json, and agents.test.tsx asserts each one has a
* recorded basis. This mark is first-party work under the repository's own
* licence, so recording it there would claim a third-party provenance it does
* not have.
*
* The path is traced from the master raster in the site repository
* (public/images/brand/oneagent-logo.png) rather than redrawn, so the geometry
* stays the designer's. It paints with fill="currentColor" instead of the
* brand's #007AFF: that value matches --blue on the light theme exactly, but the
* dark theme sets --blue to #0a84ff, so a hardcoded fill would be visibly off
* against every other blue in the window. Inheriting the colour keeps it in step
* with both themes.
*/
import brandMark from "./assets/oneagent-mark.svg?raw";

export function BrandMark({ size = 22 }: { size?: number }) {
// Inlined rather than <img src>, for the same reason the Agent marks are: an
// SVG fetched by <img> is an isolated document where currentColor resolves to
// black instead of the surrounding text colour.
return (
<span
className="brand-mark-glyph"
style={{ width: size, height: size }}
aria-hidden="true"
data-mark-kind="brand"
dangerouslySetInnerHTML={{ __html: brandMark }}
/>
);
}
6 changes: 6 additions & 0 deletions frontend/src/components/icons/asset-rights.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,11 @@
"modified": true,
"modificationNote": "Recoloured to a single currentColor glyph for theme adaptation: the linearGradient (#ff4d4d to #991b1b), the antenna stroke colour and the cyan eye highlights were removed, and the eyes became a mask cut-out. Geometry is unchanged. Not an official OpenClaw mark; drawn by the cc-switch project."
}
},
"firstPartyAssets": {
"oneagent-mark": {
"file": "assets/oneagent-mark.svg",
"note": "OneAgent's own mark, traced from the master raster in MaimoryLab/OneAgent-site (public/images/brand/oneagent-logo.png). MaimoryLab's work under this repository's Apache-2.0 license, so it carries no third-party source, licence text or copyright owner and is deliberately absent from `assets` above -- registering it there would assert a provenance it does not have. Listed here so the compliance gate can tell a first-party asset apart from one nobody attributed."
}
}
}
1 change: 1 addition & 0 deletions frontend/src/components/icons/assets/oneagent-mark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 21 additions & 1 deletion frontend/src/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@
font-size: 16px;
}

.brand-lockup span:last-child {
/* Scoped to the tagline beside the wordmark. Written as `.brand-lockup
span:last-child` this matched any last-child span anywhere in the lockup,
including the one wrapping the product mark, and painted it --text-secondary
instead of the brand colour. It only stayed hidden while the mark was a Lucide
<svg> rather than a <span>. */
.brand-lockup > div > span:last-child {
color: var(--text-secondary);
font-size: 11px;
}
Expand Down Expand Up @@ -493,6 +498,21 @@
height: 100%;
}

/* The product mark, painted in the theme's blue rather than the text colour the
Agent marks inherit. Those identify a row and should read as neutral UI; this
one is the brand and carries its colour. */
.brand-mark-glyph {
display: inline-grid;
place-items: center;
flex: 0 0 auto;
color: var(--blue);
}

.brand-mark-glyph > svg {
width: 100%;
height: 100%;
}

.agent-copy,
.choice-copy,
.progress-copy {
Expand Down
Loading
Loading