diff --git a/frontend/src/components/editor/CodeEditor.tsx b/frontend/src/components/editor/CodeEditor.tsx
index b9536757..6efaa17b 100644
--- a/frontend/src/components/editor/CodeEditor.tsx
+++ b/frontend/src/components/editor/CodeEditor.tsx
@@ -1,5 +1,6 @@
import Editor from '@monaco-editor/react';
import { useEditorStore } from '../../store/useEditorStore';
+import { useSimulatorStore } from '../../store/useSimulatorStore';
import { registerRetroAsm, LANGUAGE_ID as RETRO_ASM_ID } from './retroAsmLanguage';
function getLanguage(filename: string): string {
@@ -14,9 +15,40 @@ function getLanguage(filename: string): string {
}
export const CodeEditor = () => {
- const { files, activeFileId, setFileContent, theme, fontSize } = useEditorStore();
+ const { files, activeFileId, setFileContent, theme, fontSize, manifestViewBoardId } =
+ useEditorStore();
+ const boards = useSimulatorStore((s) => s.boards);
const activeFile = files.find((f) => f.id === activeFileId);
+ // READ-ONLY libraries.json view (the file explorer's libraries.json entry).
+ // Shows the active board's declared library manifest as plain-text JSON, live.
+ // It is read-only on purpose: adding/removing libraries is done from the
+ // Library Manager modal, which edits board.libraries (this just reflects it).
+ if (manifestViewBoardId) {
+ const b = boards.find((x) => x.id === manifestViewBoardId);
+ const content = JSON.stringify({ libraries: b?.libraries ?? [] }, null, 2);
+ return (
+
+
+
+ );
+ }
+
return (
= ({ onSaveClick, onNewCl
deleteFile,
renameFile,
setActiveGroup,
+ manifestViewBoardId,
+ setManifestView,
} = useEditorStore();
const boards = useSimulatorStore((s) => s.boards);
const activeBoardId = useSimulatorStore((s) => s.activeBoardId);
@@ -741,17 +743,21 @@ export const FileExplorer: React.FC = ({ onSaveClick, onNewCl
Clicking switches to the board and opens the Library
Manager on its list. */}
{
switchToBoard(board.id, groupId);
- window.dispatchEvent(new CustomEvent('velxio-open-library-manager'));
+ // Open the READ-ONLY libraries.json view (not the modal).
+ // Library actions happen in the Library Manager modal.
+ setManifestView(board.id);
}}
- title={`Libraries for ${boardDisplayName(board)} — click to manage (compile scope)`}
+ title={`libraries.json — ${boardDisplayName(board)}'s declared libraries (read-only; manage from the Library Manager)`}
>
-
+
- velxio.json
+ libraries.json
= ({ isOpen
[install, addToManifest, activeBoard],
);
- const uninstall = useCallback(
- async (name: string) => {
- setBusyLib(name);
- setStatusMsg(null);
- try {
- const result = await uninstallLibrary(name);
- if (result.success) {
- setStatusMsg({ type: 'success', text: `"${name}" uninstalled.` });
- fetchInstalled();
- } else {
- setStatusMsg({ type: 'error', text: result.error || `Failed to uninstall "${name}"` });
- }
- } catch (e: unknown) {
- setStatusMsg({ type: 'error', text: e instanceof Error ? e.message : 'Uninstall failed' });
- } finally {
- setBusyLib(null);
- }
- },
- [fetchInstalled],
- );
-
// A CUSTOM lib lives in the user's per-user store, so removing it hits the
// per-user delete endpoint and also drops it from the manifest.
const removeCustom = useCallback(
@@ -446,14 +424,18 @@ export const LibraryManagerModal: React.FC = ({ isOpen
{busy ? '…' : '+ Add to project'}
)}
- {row.installed && (
+ {/* Only CUSTOM uploads can be removed (per-user store). Index
+ libraries live in the shared content-addressed cache — you
+ add/remove them from THIS project, but never "uninstall" a
+ copy everyone shares, so no Uninstall button for them. */}
+ {row.custom && (
)}
diff --git a/frontend/src/store/useEditorStore.ts b/frontend/src/store/useEditorStore.ts
index 4871b5b8..94df5221 100644
--- a/frontend/src/store/useEditorStore.ts
+++ b/frontend/src/store/useEditorStore.ts
@@ -104,6 +104,12 @@ interface EditorState {
files: WorkspaceFile[];
activeFileId: string;
openFileIds: string[];
+ /** When set, the editor shows a READ-ONLY `libraries.json` view of this
+ * board's library manifest (board.libraries) instead of the active file.
+ * Cleared whenever a real file is opened/activated. Managed by the explorer's
+ * libraries.json entry; the Library Manager modal is what edits the manifest. */
+ manifestViewBoardId: string | null;
+ setManifestView: (boardId: string | null) => void;
theme: 'vs-dark' | 'light';
fontSize: number;
viewMode: EditorViewMode;
@@ -159,6 +165,8 @@ export const useEditorStore = create((set, get) => ({
files: [DEFAULT_FILE],
activeFileId: MAIN_ID,
openFileIds: [MAIN_ID],
+ manifestViewBoardId: null,
+ setManifestView: (boardId: string | null) => set({ manifestViewBoardId: boardId }),
theme: 'vs-dark',
fontSize: 14,
viewMode: 'both',
@@ -266,6 +274,7 @@ export const useEditorStore = create((set, get) => ({
return {
openFileIds: s.openFileIds.includes(id) ? s.openFileIds : [...s.openFileIds, id],
activeFileId: id,
+ manifestViewBoardId: null, // opening a real file exits the libraries.json view
openGroupFileIds: {
...s.openGroupFileIds,
[groupId]: groupOpenIds.includes(id) ? groupOpenIds : [...groupOpenIds, id],
@@ -299,6 +308,7 @@ export const useEditorStore = create((set, get) => ({
const groupId = s.activeGroupId;
return {
activeFileId: id,
+ manifestViewBoardId: null, // activating a real file exits the libraries.json view
activeGroupFileId: { ...s.activeGroupFileId, [groupId]: id },
};
});