Rebuild AgentCompany-New renderer to match design spec

- Port design tokens, sidebar, topbar, and icons to inline SVG set
- Rewrite each page (Home/Team/Work/Chat/Files/Approvals/Social/Settings)
  to drive real snapshot data and match the bento, kanban, three-pane chat,
  file tree + pinned cards, approval queue, social schedule + calendar,
  and settings sub-nav layouts
- Split Chat and Settings into per-concern subcomponents so every file
  stays under 500 lines
- Add schema v4 for pinned files and scheduled posts plus IPC + service
  wiring; cover new behavior in unit tests
- Update onboarding labels and Hire form to remove ambiguity for the
  expanded e2e suite (all settings tabs + 8 routes at 1280 and 980)
This commit is contained in:
ZacharyZhang-NY committed 2026-05-21 19:42:38 -04:00
1 parent 00736d5719
commit 74266566b7
47 files changed
+4609 -1552

No files matched your search

@@ -49,8 +49,11 @@ export class FileService {
const target = join(orgDir, `${id}-${basename(sourcePath)}`);
copyFileSync(sourcePath, target);
this.db.prepare(`
insert into file_objects(id, org_id, name, kind, size_bytes, sha256, storage_path, owner_type, owner_id, created_at)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
insert into file_objects(
id, org_id, name, kind, size_bytes, sha256, storage_path,
owner_type, owner_id, space_id, pinned, created_at, updated_at
)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
id,
orgId,
@@ -61,6 +64,9 @@ export class FileService {
target,
"system",
null,
null,
0,
now,
now,
);
this.audit.record({
@@ -74,4 +80,20 @@ export class FileService {
});
return id;
}
setPinned(orgId: string, fileId: string, pinned: boolean): void {
const result = this.db.prepare(
"update file_objects set pinned = ?, updated_at = ? where id = ? and org_id = ?",
).run(pinned ? 1 : 0, new Date().toISOString(), fileId, orgId);
if (result.changes === 0) throw new Error("File was not found.");
this.audit.record({
orgId,
entityType: "file_object",
entityId: fileId,
actorType: "employee",
actorId: null,
action: pinned ? "pinned" : "unpinned",
payload: { fileId },
});
}
}
@@ -45,6 +45,8 @@ export class SnapshotService {
.map((provider) => ({ ...provider, enabled: toBool(provider.enabled) }));
const skillBindings = list<AppSnapshot["skillBindings"][number]>(this.db, "skill_bindings", "priority asc")
.map((binding) => ({ ...binding, enabled: toBool(binding.enabled) }));
const files = list<AppSnapshot["files"][number]>(this.db, "file_objects", "created_at desc")
.map((file) => ({ ...file, pinned: toBool(file.pinned) }));
return {
activeOrgId,
@@ -62,7 +64,8 @@ export class SnapshotService {
chatThreads: list(this.db, "chat_threads", "updated_at desc"),
chatAttachments: list(this.db, "chat_attachments", "message_id asc"),
chatPresence: list(this.db, "chat_presence", "last_seen_at desc"),
files: list(this.db, "file_objects", "created_at desc"),
files,
scheduledPosts: list(this.db, "scheduled_posts", "scheduled_for asc"),
providers,
providerCatalog: PROVIDER_CATALOG,
providerRequests: list(this.db, "provider_requests", "created_at desc"),
@@ -1,6 +1,10 @@
import type { DatabaseSync } from "node:sqlite";
import { BrowserWindow, session } from "electron";
import type { CreateSocialAccountInput } from "../../shared/commands";
import type {
CreateScheduledPostInput,
CreateSocialAccountInput,
SetScheduledPostStateInput,
} from "../../shared/commands";
import { SOCIAL_PLATFORM, type SocialPlatform } from "../../shared/domain";
import { createId } from "../../shared/ids";
import { AuditService } from "./audit-service";
@@ -26,6 +30,63 @@ export class SocialService {
this.audit = new AuditService(db);
}
createScheduledPost(input: CreateScheduledPostInput): string {
const account = this.db.prepare("select id from social_accounts where id = ? and org_id = ?")
.get(input.socialAccountId, input.orgId) as { id: string } | undefined;
if (!account) throw new Error("Social account was not found.");
const body = input.bodyMd.trim();
if (!body) throw new Error("Post body is required.");
const scheduledFor = input.scheduledFor.trim();
if (!scheduledFor) throw new Error("Scheduled date is required.");
const id = createId("spost");
const now = new Date().toISOString();
this.db.prepare(`
insert into scheduled_posts(
id, org_id, social_account_id, body_md, scheduled_for,
state, approval_id, created_by_employee_id, created_at, updated_at
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
id,
input.orgId,
input.socialAccountId,
body,
scheduledFor,
input.state,
null,
input.createdByEmployeeId,
now,
now,
);
this.audit.record({
orgId: input.orgId,
entityType: "scheduled_post",
entityId: id,
actorType: input.createdByEmployeeId ? "employee" : "system",
actorId: input.createdByEmployeeId,
action: "scheduled",
payload: { socialAccountId: input.socialAccountId, scheduledFor, state: input.state },
});
return id;
}
setScheduledPostState(input: SetScheduledPostStateInput): void {
const now = new Date().toISOString();
const result = this.db.prepare(`
update scheduled_posts set state = ?, updated_at = ?
where id = ? and org_id = ?
`).run(input.state, now, input.scheduledPostId, input.orgId);
if (result.changes === 0) throw new Error("Scheduled post was not found.");
this.audit.record({
orgId: input.orgId,
entityType: "scheduled_post",
entityId: input.scheduledPostId,
actorType: "employee",
actorId: null,
action: `state_${input.state}`,
payload: { state: input.state },
});
}
createAccount(input: CreateSocialAccountInput): string {
const id = createId("soc");
const now = new Date().toISOString();