fix: restore remaining merge regressions (SKILL.md, MIME types, showHidden, symlink, activeProfileHint)
This commit is contained in:
parent
57b7d8bd0f
commit
c8ae7acbf4
@ -18,6 +18,8 @@ const MIME_MAP: Record<string, string> = {
|
||||
wav: "audio/wav",
|
||||
ogg: "audio/ogg",
|
||||
pdf: "application/pdf",
|
||||
html: "text/html",
|
||||
htm: "text/html",
|
||||
};
|
||||
|
||||
/** Extensions recognized as code files for syntax-highlighted viewing. */
|
||||
|
||||
@ -33,6 +33,8 @@ const MIME_MAP: Record<string, string> = {
|
||||
m4a: "audio/mp4",
|
||||
// Documents
|
||||
pdf: "application/pdf",
|
||||
html: "text/html",
|
||||
htm: "text/html",
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -437,6 +437,7 @@ export function Sidebar({
|
||||
<ProfileSwitcher
|
||||
onProfileSwitch={handleProfileSwitch}
|
||||
onCreateWorkspace={() => setShowCreateWorkspace(true)}
|
||||
activeProfileHint={String(sidebarRefreshKey)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -28,6 +28,8 @@ export type TreeNode = {
|
||||
children?: TreeNode[];
|
||||
/** When true, the node represents a virtual folder/file outside the real workspace (e.g. Skills, Memories). CRUD ops are disabled. */
|
||||
virtual?: boolean;
|
||||
/** True when the entry is a symbolic link / shortcut. */
|
||||
symlink?: boolean;
|
||||
};
|
||||
|
||||
/** Folder names reserved for virtual sections -- cannot be created/renamed to. */
|
||||
@ -153,6 +155,14 @@ function LockBadge() {
|
||||
);
|
||||
}
|
||||
|
||||
function SymlinkBadge() {
|
||||
return (
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ opacity: 0.55 }}>
|
||||
<path d="m9 18 6-6-6-6" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ChevronIcon({ open }: { open: boolean }) {
|
||||
return (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
||||
@ -563,6 +573,13 @@ function DraggableNode({
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Symlink indicator */}
|
||||
{node.symlink && !compact && (
|
||||
<span className="flex-shrink-0 ml-0.5" title="Symbolic link" style={{ color: "var(--color-text-muted)" }}>
|
||||
<SymlinkBadge />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Type badge for objects */}
|
||||
{node.type === "object" && (
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded-full flex-shrink-0"
|
||||
|
||||
@ -20,11 +20,13 @@ export type ProfileSwitcherTriggerProps = {
|
||||
type ProfileSwitcherProps = {
|
||||
onProfileSwitch?: () => void;
|
||||
onCreateWorkspace?: () => void;
|
||||
/** Parent-tracked active profile -- triggers a re-fetch when it changes (e.g. after workspace creation). */
|
||||
activeProfileHint?: string | null;
|
||||
/** When set, this renders instead of the default button; dropdown still opens below. */
|
||||
trigger?: (props: ProfileSwitcherTriggerProps) => React.ReactNode;
|
||||
};
|
||||
|
||||
export function ProfileSwitcher({ onProfileSwitch, onCreateWorkspace, trigger }: ProfileSwitcherProps) {
|
||||
export function ProfileSwitcher({ onProfileSwitch, onCreateWorkspace, activeProfileHint, trigger }: ProfileSwitcherProps) {
|
||||
const [profiles, setProfiles] = useState<ProfileInfo[]>([]);
|
||||
const [activeProfile, setActiveProfile] = useState("default");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -44,7 +46,7 @@ export function ProfileSwitcher({ onProfileSwitch, onCreateWorkspace, trigger }:
|
||||
|
||||
useEffect(() => {
|
||||
void fetchProfiles();
|
||||
}, [fetchProfiles]);
|
||||
}, [fetchProfiles, activeProfileHint]);
|
||||
|
||||
// Close dropdown on outside click
|
||||
useEffect(() => {
|
||||
|
||||
@ -46,6 +46,10 @@ type WorkspaceSidebarProps = {
|
||||
width?: number;
|
||||
/** Called after the user switches to a different profile. */
|
||||
onProfileSwitch?: () => void;
|
||||
/** Whether hidden (dot) files/folders are currently shown. */
|
||||
showHidden?: boolean;
|
||||
/** Toggle hidden files visibility. */
|
||||
onToggleHidden?: () => void;
|
||||
/** Called when the user clicks the collapse/hide sidebar button. */
|
||||
onCollapse?: () => void;
|
||||
};
|
||||
@ -404,6 +408,8 @@ export function WorkspaceSidebar({
|
||||
onClose,
|
||||
activeProfile,
|
||||
onProfileSwitch,
|
||||
showHidden,
|
||||
onToggleHidden,
|
||||
width: widthProp,
|
||||
onCollapse,
|
||||
}: WorkspaceSidebarProps) {
|
||||
@ -488,6 +494,7 @@ export function WorkspaceSidebar({
|
||||
<ProfileSwitcher
|
||||
onProfileSwitch={onProfileSwitch}
|
||||
onCreateWorkspace={() => setShowCreateWorkspace(true)}
|
||||
activeProfileHint={activeProfile}
|
||||
trigger={({ isOpen, onClick, activeProfile: profileName, switching }) => (
|
||||
<button
|
||||
type="button"
|
||||
@ -595,7 +602,43 @@ export function WorkspaceSidebar({
|
||||
>
|
||||
ironclaw.sh
|
||||
</a>
|
||||
<ThemeToggle />
|
||||
<div className="flex items-center gap-0.5">
|
||||
{onToggleHidden && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggleHidden}
|
||||
className="p-1.5 rounded-lg transition-colors"
|
||||
style={{ color: showHidden ? "var(--color-accent)" : "var(--color-text-muted)" }}
|
||||
title={showHidden ? "Hide dotfiles" : "Show dotfiles"}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
{showHidden ? (
|
||||
<>
|
||||
<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" />
|
||||
<path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />
|
||||
<path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" />
|
||||
<path d="m2 2 20 20" />
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
@ -358,6 +358,7 @@ function WorkspacePageInner() {
|
||||
reconnect: reconnectWorkspace,
|
||||
browseDir, setBrowseDir, parentDir: browseParentDir, workspaceRoot, openclawDir,
|
||||
activeProfile,
|
||||
showHidden, setShowHidden,
|
||||
} = useWorkspaceWatcher();
|
||||
|
||||
// handleProfileSwitch is defined below fetchSessions/fetchCronJobs (avoids TDZ)
|
||||
@ -1325,6 +1326,8 @@ function WorkspacePageInner() {
|
||||
onExternalDrop={handleSidebarExternalDrop}
|
||||
activeProfile={activeProfile}
|
||||
onProfileSwitch={handleProfileSwitch}
|
||||
showHidden={showHidden}
|
||||
onToggleHidden={() => setShowHidden((v) => !v)}
|
||||
mobile
|
||||
onClose={() => setSidebarOpen(false)}
|
||||
/>
|
||||
@ -1360,6 +1363,8 @@ function WorkspacePageInner() {
|
||||
onExternalDrop={handleSidebarExternalDrop}
|
||||
activeProfile={activeProfile}
|
||||
onProfileSwitch={handleProfileSwitch}
|
||||
showHidden={showHidden}
|
||||
onToggleHidden={() => setShowHidden((v) => !v)}
|
||||
width={leftSidebarWidth}
|
||||
onCollapse={() => setLeftSidebarCollapsed(true)}
|
||||
/>
|
||||
|
||||
@ -667,6 +667,19 @@ VALUES ('Roadmap', 'map', 'projects/roadmap.md', '<parent_doc_id>', 0);
|
||||
- **Field names**: human-readable, proper capitalization ("Email Address" not "email")
|
||||
- **Be descriptive**: "Phone Number" not "Phone"
|
||||
- **Be consistent**: Don't mix "Full Name" and "Name" in the same object
|
||||
- **TRIPLE ALIGNMENT (MANDATORY)**: The DuckDB object `name`, the filesystem directory name, and the `.object.yaml` `name` field MUST all be identical. If any one of these three diverges, the UI will fail to render the object. For example, if DuckDB has `name = 'contract'`, the directory MUST be `contract/` (in workspace) and the yaml MUST have `name: "contract"`. Never use plural for one and singular for another.
|
||||
|
||||
### Renaming / Moving Objects
|
||||
|
||||
When renaming or relocating an object, you MUST update ALL THREE in a single operation:
|
||||
|
||||
1. **DuckDB**: Update `objects.name` (if FK constraints block this, recreate the object with the new name and migrate entries)
|
||||
2. **Directory**: `mv` the old directory to the new name
|
||||
3. **`.object.yaml`**: Update the `name` field to match
|
||||
4. **PIVOT view**: `DROP VIEW IF EXISTS v_{old_name}; CREATE OR REPLACE VIEW v_{new_name} ...`
|
||||
5. **Verify**: Confirm all three match and the view returns data
|
||||
|
||||
Never rename partially. If you can't complete all steps, don't start the rename — explain the constraint to the user first.
|
||||
|
||||
## Error Handling
|
||||
|
||||
@ -872,7 +885,7 @@ After creating a `.report.json` file:
|
||||
## Critical Reminders
|
||||
|
||||
- Handle the ENTIRE CRM operation from analysis to SQL execution to filesystem projection to summary
|
||||
- **NEVER SKIP FILESYSTEM PROJECTION**: After creating/modifying any object, you MUST create/update `~/.openclaw/workspace/{object}/.object.yaml` AND the `v_{object}` view. If you skip this, the object will be invisible in the sidebar. This is NOT optional.
|
||||
- **NEVER SKIP FILESYSTEM PROJECTION**: After creating/modifying any object, you MUST create/update `{object}/.object.yaml` in workspace AND the `v_{object}` view. If you skip this, the object will be invisible in the sidebar. This is NOT optional.
|
||||
- **THREE STEPS, EVERY TIME**: (1) SQL transaction, (2) filesystem projection (.object.yaml + directory), (3) verify. An operation is NOT complete until all three are done.
|
||||
- Always check existing data before creating (`SELECT` before `INSERT`, or `ON CONFLICT`)
|
||||
- Use views (`v_{object}`) for all reads — never write raw PIVOT queries for search
|
||||
@ -890,8 +903,9 @@ After creating a `.report.json` file:
|
||||
- **workspace_context.yaml**: READ-ONLY. Never modify. Data flows from Dench UI only.
|
||||
- **Source of truth**: DuckDB for all structured data. Filesystem for document content and navigation tree. Never duplicate entry data to the filesystem.
|
||||
- **ENTRY COUNT**: After adding entries, update `entry_count` in `.object.yaml`.
|
||||
- **NEVER POLLUTE THE WORKSPACE**: Always keep cleaning / organising the workspace to something more nicely structured. Always look out for bloat and too many random files scattered around everywhere for no reason, every time you do any actions in filesystem always try to come up with the most efficient and nice file system structure inside `~/.openclaw/workspace`.
|
||||
- **TEMPORARY FILES**: All temporary scripts / code / text / other files as and when needed for processing must go into `~/.openclaw/workspace/tmp/` directory (create it if it doesn't exist, only if needed).
|
||||
- **NAME CONSISTENCY**: The DuckDB `objects.name`, the filesystem directory name, and `.object.yaml` `name` MUST be identical. A mismatch between ANY of these three will break the UI. Before finishing any object creation or modification, verify: `objects.name == directory_name == yaml.name`. See "Renaming / Moving Objects" under Naming Conventions.
|
||||
- **NEVER POLLUTE THE WORKSPACE**: Always keep cleaning / organising the workspace to something more nicely structured. Always look out for bloat and too many random files scattered around everywhere for no reason, every time you do any actions in filesystem always try to come up with the most efficient and nice file system structure inside the workspace.
|
||||
- **TEMPORARY FILES**: All temporary scripts / code / text / other files as and when needed for processing must go into `tmp/` directory (create it in the workspace if it doesn't exist, only if needed).
|
||||
|
||||
## Browser Use
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user