Files
galaxy/client/package.json
T
John Chilton 4afd63a9a9 History notebooks.
1. What Are History Pages?

History Pages are markdown documents tied to Galaxy histories. They let users (and AI agents) document, annotate, and share analysis narratives alongside the data that produced them. A history can have multiple pages, and each page supports an AI assistant that can read history contents and propose edits.

History Pages are built on the existing Galaxy **Page** model — they are regular Pages with an optional `history_id` foreign key. This unified model means history-attached and standalone pages share the same editor, revision system, AI chat, and API surface. The difference is contextual: history pages gain access to history-aware AI tools and are accessed through the history panel rather than the pages grid.

Every save creates an immutable revision, edits from humans and agents are tracked separately via `edit_source`, and the revision system supports preview and one-click rollback.

---

2. Standalone Pages vs History Pages

The system supports two page contexts through a single unified editor (`PageEditorView`):

| Aspect | Standalone Pages | History Pages |
|--------|-----------------|---------------|
| **Entry point** | Grid list (`/pages/list`) or direct URL | History panel "Pages" button |
| **Route** | `/pages/editor?id=X` | `/histories/:historyId/pages/:pageId` |
| **`history_id`** | null | Set — scopes page to a history |
| **AI chat tools** | Text editing only (no history tools) | Full history tools: `list_history_datasets`, `get_dataset_info`, `get_dataset_peek`, `get_collection_structure`, `resolve_hid` |
| **Drag-and-drop** | From toolbox directives | Also from history panel (datasets/collections) |
| **Permissions modal** | Yes (`ObjectPermissionsModal`) | No — inherits from history sharing |
| **Save & View** | Yes (slug-based published URL) | No (history context, no slug) |
| **Page list** | Grid (`/pages/list`) | Inline `HistoryPageList` within history panel |
| **Window Manager** | "View in Window" grid action | Click from list opens in WinBox |
| **Auto-create** | No | `resolveCurrentPage()` creates on first visit |

Both modes share: editor UI, revision system, AI chat, dirty tracking, diff views, and the same API endpoints.

---

3. User Stories

Researcher documenting an analysis

> *"I ran a ChIP-seq pipeline and want to write up what I did and what the results mean, with embedded dataset previews and plots, right next to the history that contains the data."*

- Opens history panel -> clicks "Pages" button
- System auto-creates a page titled after the history
- Types markdown prose; uses the toolbox to insert dataset references (`history_dataset_display(...)`)
- Drags a dataset from the history panel into the editor -- directive auto-inserted
- Clicks Preview to see rendered markdown with live dataset embeds
- Saves; revision #1 recorded with `edit_source="user"`

AI-assisted page editing

> *"I have 50 datasets from a variant-calling run. I want the AI to summarize what's in my history and draft a methods section."*

- Opens page -> toggles chat panel (split view: 60% editor / 40% chat)
- Types: "Summarize the datasets in this history and draft a Methods section"
- Agent calls `list_history_datasets` -> `get_dataset_info` -> `resolve_hid` tools
- Agent returns a `section_patch` proposal targeting `## Methods`
- User sees a per-section diff with checkboxes; accepts the Methods section, rejects the Introduction rewrite
- Applied content creates revision with `edit_source="agent"`
- Conversation persists across panel close/reopen and page refresh

Sharing and publishing

> *"My analysis is complete. I want to share the page read-only."*

- Shared histories expose their pages in read-only display mode to other users
- Standalone pages use the Permissions modal to manage sharing, publishing, and slug assignment

Revision history and rollback

> *"The agent's last edit broke my formatting. I want to go back."*

- Opens revision panel (right sidebar, 300px)
- Sees revision list with timestamps and source badges: "Manual", "AI", "Restored"
- Clicks an old revision to preview it read-only
- Clicks "Restore" -> creates a new revision from old content (`edit_source="restore"`)

---

4. Architecture Overview

```
+---------------------------------------------------------------------------+
|                          Frontend (Vue 3)                                  |
|                                                                           |
|  HistoryCounter --> HistoryPageView --> PageEditorView <-- PageEditor      |
|       |                 |    |              |                  |           |
|       |           +-----+    +-----+   MarkdownEditor     (standalone     |
|       |           |                |    TextEditor          entry)         |
|       |     HistoryPageList        |    (drag-drop)                       |
|       |                      EditorSplitView                              |
|       |                            |                                      |
|       |   PageRevisionList    PageChatPanel                               |
|       |   PageRevisionView     |         |                                |
|       |                  ChatMessageCell  ProposalDiffView                 |
|       |                  ChatInput       SectionPatchView                  |
|       |                                                                   |
|  pageEditorStore (Pinia) <---> API Client (api/pages.ts)                  |
+---------------------------------+-----------------------------------------+
                                  | REST
+---------------------------------v-----------------------------------------+
|                       Backend (FastAPI)                                    |
|                                                                           |
|  /api/pages (history_id filter) --> PageManager                           |
|  /api/pages/{id}/revisions      --> PageManager (revisions)               |
|  /api/chat (page_id)            --> ChatManager + AgentService            |
|                                          |                                |
|                                  PageAssistantAgent                       |
|                                    +- list_history_datasets               |
|                                    +- get_dataset_info                    |
|                                    +- get_dataset_peek                    |
|                                    +- get_collection_structure            |
|                                    +- resolve_hid                         |
|                                                                           |
|  Models: Page (+ history_id), PageRevision (+ edit_source), ChatExchange  |
|  markdown_util.py: ready_galaxy_markdown_for_export()                     |
+---------------------------------------------------------------------------+
```

---

5. Data Model

Page (extended)

| Column | Type | Notes |
|--------|------|-------|
| `id` | int PK | |
| `user_id` | int FK -> galaxy_user | Indexed |
| `history_id` | int FK -> history | **Nullable**, indexed. When set, page is history-attached |
| `title` | text | Not versioned |
| `slug` | text | Indexed. Standalone pages only |
| `latest_revision_id` | int FK -> page_revision | Eager-loaded; circular FK with `use_alter` |
| `source_invocation_id` | int FK -> workflow_invocation | Nullable. Tracks "generated from invocation" |
| `published` / `importable` | bool | Standalone sharing features |
| `deleted` | bool | Soft-delete pattern |
| `create_time` / `update_time` | datetime | |

Relationships: `user`, `history` (optional), `revisions` (cascade delete), `latest_revision` (eager), `source_invocation`, `tags`, `annotations`, `ratings`, `users_shared_with`

PageRevision (extended)

| Column | Type | Notes |
|--------|------|-------|
| `id` | int PK | |
| `page_id` | int FK -> page | Indexed |
| `title` | text | Snapshot of title at revision time |
| `content` | text | Raw markdown with internal IDs |
| `content_format` | varchar(32) | `"markdown"` or `"html"` |
| `edit_source` | varchar(16) | **New.** `"user"`, `"agent"`, or `"restore"` |
| `create_time` / `update_time` | datetime | |

ChatExchange (extended)

| Column | Type | Notes |
|--------|------|-------|
| `page_id` | int FK -> page | Nullable, indexed. Scopes chat to a page |

The original `notebook_id` FK was replaced with `page_id` when the HistoryNotebook model was merged into Page.

6. Content Pipeline

Page content flows through two representations:

```
User edits markdown in MarkdownEditor / TextEditor
        |
        v
  +-------------------+
  |  Raw content       |  Stored in DB as-is
  |  (internal IDs)    |  history_dataset_id=42
  +--------+----------+
           |  rewrite_content_for_export()
           v
  +-----------------------+     +----------------------------+
  |  content_editor        |     |  content                    |
  |  (raw, for editor)     |     |  (encoded IDs + expanded    |
  |  Same as DB content    |     |   directives, for render)   |
  +-----------------------+     +----------------------------+
```

The API returns **both** fields in `PageDetails`:
- `content_editor`: What the text editor displays and saves back
- `content`: What the Markdown renderer uses (with encoded IDs the existing Galaxy markdown components expect)

This dual-field pattern avoids the round-trip problems that would arise from encoding/decoding IDs on every save cycle.

---

7. Agent Architecture

PageAssistantAgent

Registered as `AgentType.PAGE_ASSISTANT` in the Galaxy agent framework. Uses pydantic-ai with structured output.

**Tools (5):**

| Tool | Purpose | Returns |
|------|---------|---------|
| `list_history_datasets` | Paginated history item listing | HID, name, type, state, size, internal ID |
| `get_dataset_info` | Detailed metadata for one HID | Name, format, state, size, tool info, metadata |
| `get_dataset_peek` | Pre-computed content preview | First lines of dataset content |
| `get_collection_structure` | Collection element listing | Element names, types, states |
| `resolve_hid` | HID -> directive argument conversion | `history_dataset_id=N` or `history_dataset_collection_id=N` + `job_id` |

When editing a standalone page (no `history_id`), history tools are unavailable -- the agent can still do full-replacement and section-patch edits on the page content.

**Output types (3, discriminated by `mode` literal):**

| Type | When Used | Content |
|------|-----------|---------|
| `FullReplacementEdit` | Complete document rewrite | Full new markdown document |
| `SectionPatchEdit` | Targeted heading-level edit | Target heading + new section content |
| `str` (plain text) | Conversational response | No edit proposal |

**System prompt** is dynamically assembled:
1. Static instructions from `prompts/page_assistant.md`
2. Auto-generated directive reference table (reads `markdown_parse.VALID_ARGUMENTS` at runtime)
3. Current page content injected as context
4. History name and item count summary (when `history_id` is set)

The agent works in HID-space (matching what users see in the history panel) and uses `resolve_hid` to translate to the `history_dataset_id=N` directive arguments that Galaxy's markdown renderer expects.

Chat Persistence

Conversations are scoped per-page via `ChatExchange.page_id`. The flow:

1. User sends message -> `POST /api/chat` with `page_id` and `agent_type="page_assistant"`
2. API looks up page, extracts `history_id` and current content from the page record
3. Agent processes with history tools (if history-attached) and current document context
4. Response stored as `ChatExchange` + `ChatExchangeMessage` with full `agent_response` JSON
5. Frontend persists `exchange_id` in `userLocalStorage` per-page for session continuity

---

8. Frontend Components

Component Tree

```
History-attached entry:
  HistoryCounter (button in history panel)
    +- HistoryPageView (list + display routing -- 176 lines)
         +- HistoryPageList (page picker -- 89 lines)
         +- Markdown (display-only render)
         +- PageEditorView (edit mode delegation)

Standalone entry:
  PageEditor (thin wrapper -- 13 lines)
    +- PageEditorView

PageEditorView (unified editor -- 364 lines)
  +- ClickToEdit (inline title editing)
  +- MarkdownEditor
  |    +- TextEditor (drag-and-drop for history items)
  +- PageRevisionList (sidebar panel -- 88 lines)
  +- PageRevisionView (read-only revision preview -- 59 lines)
  +- EditorSplitView (resizable 60/40 split -- 111 lines)
  |    +- PageChatPanel (agent chat -- 477 lines)
  |         +- ChatMessageCell (shared from ChatGXY)
  |         +- ChatInput (shared from ChatGXY)
  |         +- ProposalDiffView (full-doc diff -- 123 lines)
  |         +- SectionPatchView (per-section diff -- 207 lines)
  +- ObjectPermissionsModal (standalone only -- 16 lines)
  |    +- ObjectPermissions (344 lines)
```

PageEditorView (unified editor)

The core editor component. Adapts based on context:

| Feature | `historyId` set | standalone |
|---------|----------------|------------|
| Back button target | `/histories/:hid/pages` | `/pages/list` |
| Title display | History name (read-only header) | Inline `ClickToEdit` |
| Revisions button | Always | Always |
| Chat button | When agents configured | When agents configured |
| Permissions button | Hidden | Shown |
| Save & View | Hidden | Shown |
| Preview | Navigates with `displayOnly=true` | Opens in Window Manager or navigates |

**View states** (template branching):
1. Loading spinner (no current page yet)
2. Error alert (dismissible)
3. Display-only mode (read-only Markdown render + toolbar with Edit button)
4. Revision view (full-page revision preview with Restore button)
5. Edit mode (toolbar + editor + optional chat/revision sidepanels)

HistoryPageView (history context router)

Routes between three states for history-attached pages:
1. **List mode** (no `pageId`) -> `HistoryPageList`
2. **Display mode** (`displayOnly=true`) -> Markdown renderer with toolbar
3. **Edit mode** (`pageId` set, no `displayOnly`) -> delegates to `PageEditorView`

Handles Window Manager integration: when WM is active, clicking a page in the list opens it in a WinBox window via `displayOnly=true` with `router.push(url, { title, preventWindowManager: false })`.

Pinia Store (`pageEditorStore` -- 472 lines)

**Mode:** `mode: "history" | "standalone"` -- controls which features are available.

**State management:**
- Page list, current page, editor content (raw), title
- Dirty tracking: `isDirty = currentContent !== originalContent || currentTitle !== originalTitle`
- Revision list and selected revision
- UI toggles: `showRevisions`, `showChatPanel` (mutually exclusive)
- Loading/saving flags

**Cross-session persistence (userLocalStorage):**
- `currentPageIds` -- remembers which page was open per-history
- `currentChatExchangeIds` -- remembers chat exchange per-page
- `dismissedChatProposals` -- remembers dismissed proposals per-page

**Smart defaults:**
- `resolveCurrentPage(historyId)` returns stored page, falls back to most recent by update_time, or auto-creates a new one

**Mode differentiation is minimal** -- mostly a UI/UX signal:
- History mode: guard checks require `historyId` for load operations
- Standalone mode: `savePage()` defaults `edit_source` to `"user"` if not specified
- API calls are identical -- unified `/api/pages` endpoints handle both via optional `history_id`

Diff System (`sectionDiffUtils.ts` -- 218 lines)

Built on [jsdiff](https://github.com/kpdecker/jsdiff) (`diff@^8.0.3`).

| Function | Purpose |
|----------|---------|
| `markdownSections(content)` | Split document by `#{1,6}` headings |
| `computeLineDiff(old, new)` | Line-level unified diff |
| `sectionDiff(old, new)` | Per-section change detection |
| `applySectionPatches(old, new, accepted)` | Merge only accepted section changes |
| `applySectionEdit(content, heading, newContent)` | Replace single section |
| `diffStats(changes)` | Count additions/deletions |

**Stale proposal detection:** Uses DJB2 hash of original content. If page content changes after a proposal was generated, Accept buttons are disabled.

Routes

| Path | Component | Notes |
|------|-----------|-------|
| `/histories/:historyId/pages` | HistoryPageView | List mode |
| `/histories/:historyId/pages/:pageId` | HistoryPageView | Edit mode |
| `/histories/:historyId/pages/:pageId?displayOnly=true` | HistoryPageView | Read-only rendered (WM) |
| `/pages/editor?id=X` | PageEditor | Standalone edit |
| `/pages/editor?id=X&displayOnly=true` | PageEditor | Standalone display |
| `/pages/list` | GridPage | Standalone page grid |
| `/published/page?id=X` | PageView | Published/embed view |

Drag-and-Drop

TextEditor supports drag from the history panel when `mode="page"`:
- Uses Galaxy's `eventStore.getDragItems()` infrastructure
- Datasets -> `history_dataset_display(history_dataset_id=...)` directive
- Collections -> `history_dataset_collection_display(history_dataset_collection_id=...)` directive
- Visual feedback: green dashed border on valid dragover

Window Manager Integration

When Galaxy's Window Manager (WinBox) is active:
- **History pages:** Clicking a page in `HistoryPageList` opens it in a WinBox window via `displayOnly=true`
- **Standalone pages:** "View in Window" grid action calls `Galaxy.frame.add()` with embed URL
- **HistoryCounter:** The page button respects WM state -- opens in frame when active
- `onUnmounted` skips `store.$reset()` in display mode (iframe independence)

---

9. API Surface

All page operations use the unified `/api/pages` endpoints. History-attached pages are just pages with `history_id` set.

Page CRUD

| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/api/pages` | List pages (supports `history_id` filter) |
| `POST` | `/api/pages` | Create page (with optional `history_id`) |
| `GET` | `/api/pages/{id}` | Get page (two content fields: `content` + `content_editor`) |
| `PUT` | `/api/pages/{id}` | Update (creates new revision with `edit_source`) |
| `DELETE` | `/api/pages/{id}` | Soft-delete |
| `PUT` | `/api/pages/{id}/undelete` | Restore |

Revisions

| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/api/pages/{id}/revisions` | List revisions |
| `GET` | `/api/pages/{id}/revisions/{rid}` | Get revision content |
| `POST` | `/api/pages/{id}/revisions/{rid}/revert` | Restore to revision (`edit_source="restore"`) |

Sharing & Publishing (standalone pages)

| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/api/pages/{id}/sharing` | Current sharing status |
| `PUT` | `/api/pages/{id}/enable_link_access` | Enable link sharing |
| `PUT` | `/api/pages/{id}/publish` | Publish page |
| `PUT` | `/api/pages/{id}/share_with_users` | Share with specific users |
| `PUT` | `/api/pages/{id}/slug` | Set URL slug |

Chat

| Method | Path | Purpose |
|--------|------|---------|
| `POST` | `/api/chat` | Send message (with `page_id` + `agent_type`) |
| `GET` | `/api/chat/page/{page_id}/history` | Retrieve page chat history |

Index Query Parameters

| Param | Default | Notes |
|-------|---------|-------|
| `history_id` | null | Filter pages by history (the key filter for history-attached pages) |
| `show_own` | true | Show user's own pages |
| `show_published` | true | Show published pages |
| `show_shared` | false | Show pages shared with user |
| `search` | null | Freetext search |
| `sort_by` | -- | `create_time`, `title`, `update_time`, `username` |
| `limit` / `offset` | 100 / 0 | Pagination |

---

10. Test Coverage

Summary

| Layer | Tests | LOC | Coverage |
|-------|-------|-----|----------|
| Selenium E2E | 24 | 489 | Navigation, editing, drag-drop, WM, revisions, rename |
| API integration | in `test_pages_history_attached.py` | 13,561 | CRUD, revisions, permissions |
| Vitest (components) | 9 test files | 2,241 | All PageEditor components, store, diff utils |
| Agent unit | 28 | 708 | Structured output, tools, prompt injection, live LLM |
| History tools | 32 | 511 | All 5 tool functions |
| Chat manager | 8 | 149 | Page-scoped persistence, filtering |

Frontend Test Files

| File | Lines | Focus |
|------|-------|-------|
| `PageEditorView.test.ts` | 645 | Unified editor: standalone + history modes, revisions, WM |
| `HistoryPageView.test.ts` | 367 | List/display/edit routing, lifecycle, WM integration |
| `PageChatPanel.test.ts` | 379 | Chat loading, proposals, feedback, staleness |
| `sectionDiffUtils.test.ts` | 265 | Section parsing, diff computation, patch application |
| `PageRevisionList.test.ts` | 207 | Revision list rendering, source labels, restore |
| `HistoryPageList.test.ts` | 185 | Page list, create/select/view events |
| `ProposalDiffView.test.ts` | 66 | Full-replacement diff rendering |
| `SectionPatchView.test.ts` | 68 | Section-level patch UI |
| `EditorSplitView.test.ts` | 59 | Resizable split layout |
| `pageEditorStore.test.ts` | 952 | Store: CRUD, revisions, persistence, standalone mode |

Test Infrastructure

- **Selenium helpers:** 11 methods on `NavigatesGalaxy` (navigate, create, edit, save, rename, revisions)
- **Navigation YAML:** 25+ selectors under `pages.history` section
- **Vitest:** Pinia testing utilities, MSW for HTTP mocking, Vue shallowMount
- **Agent tests:** Mocked pydantic-ai agent + optional live LLM tests (env-gated)

---

11. ChatGXY Extraction

The existing `ChatGXY.vue` (982 lines) was refactored into shared sub-components before building the page chat panel:

| Component | Lines | Purpose |
|-----------|-------|---------|
| `ChatMessageCell.vue` | 110 | Message rendering with role styling, feedback buttons, action suggestions |
| `ChatInput.vue` | ~40 | Textarea + send button with busy state |
| `ActionCard.vue` | 80 | Action suggestion cards with priority-based styling |
| `agentTypes.ts` | 59 | Agent type registry with icons and labels |
| `chatTypes.ts` | 15 | Shared `ChatMessage` interface |
| `chatUtils.ts` | 12 | `generateId()` and `scrollToBottom()` helpers |

---

12. Design Decisions

Model Merge: HistoryNotebook -> Page

The original implementation created separate `HistoryNotebook` and `HistoryNotebookRevision` tables. After removing HID syntax (which was the only structural difference between notebooks and pages), the models were identical. The merge:

- Added `page.history_id` (nullable FK to history) instead of a separate table
- Added `page_revision.edit_source` to track revision provenance
- Changed `chat_exchange.notebook_id` -> `chat_exchange.page_id`
- Eliminated separate API endpoints (`/api/histories/{id}/notebooks/*`), manager, and schema classes
- All page operations now go through the unified `/api/pages` endpoints with optional `history_id` filter

**Benefit:** One model, one API, one editor, one store. No duplication.

HID Syntax (Decided: Removed from storage layer)

History pages originally introduced `hid=N` syntax in stored markdown -- ~630 lines across 16 files for backend resolution, dual content fields, client-side provide/inject, and store-based HID-to-ID mapping.

**Current approach:** Pages store `history_dataset_id=X` (matching existing Page syntax). The agent uses `resolve_hid` as a tool to bridge between user-visible HIDs and directive IDs. This eliminates the resolution machinery while preserving the agent's ability to work with HIDs naturally.

Trade-off: power users hand-editing markdown see opaque IDs, but the toolbox and drag-and-drop handle insertion -- most users never read raw markdown.

UI Convergence

Two parallel editors existed: legacy `PageEditorMarkdown.vue` (Options API, local state, no revisions/chat) and `HistoryNotebookView.vue` (Composition API, Pinia store, full features). The convergence:

- Created `PageEditorView.vue` as a single editor that adapts via `mode: "history" | "standalone"`
- `HistoryPageView.vue` kept only list + display routing; edit mode delegates to `PageEditorView`
- Legacy `PageEditorMarkdown.vue` and `PageEditor/services.js` deleted
- Single `pageEditorStore` handles both modes with minimal branching

Multiple Pages Per History

No unique constraint on `page.history_id`. A history can have multiple pages for different analysis perspectives, collaborators, or document types.

Title Not Versioned

Title lives on `Page`, not on revisions. Renaming doesn't create a new revision -- it's page identity, not content. (PageRevision does have a `title` field for snapshot purposes.)

Revision = Append-Only

Every edit (user save, agent apply, restore) creates a new `PageRevision`. No in-place updates. `edit_source` tracks provenance.

Section-Level Patching

The agent can propose section-level edits (targeted by heading). The frontend shows per-section diffs with individual checkboxes. Users accept/reject sections independently. This is more practical than all-or-nothing for large documents.

Panel Mutual Exclusion

The revision panel and chat panel are mutually exclusive -- toggling one closes the other. This avoids layout complexity and keeps the editor area usable.
2026-05-20 05:36:28 -04:00

222 lines
6.9 KiB
JSON

{
"name": "@galaxyproject/galaxy-client",
"version": "26.1.dev0",
"packageManager": "pnpm@10.26.1",
"description": "Galaxy client application build system",
"keywords": [
"galaxy"
],
"repository": {
"type": "git",
"url": "https://github.com/galaxyproject/galaxy.git"
},
"license": "MIT",
"files": [
"dist"
],
"browserslist": [
"defaults",
"not op_mini all",
"not ios_saf <= 15.0",
"not kaios > 0"
],
"pnpm": {
"overrides": {
"vue": "2.7.16",
"@fortawesome/fontawesome-common-types": "6.2.1"
},
"peerDependencyRules": {
"allowedVersions": {
"@vitejs/plugin-vue2>vite": "^8.0.0",
"@rollup/plugin-inject>rollup": "*"
}
},
"onlyBuiltDependencies": [
"esbuild",
"vue-demi"
]
},
"dependencies": {
"@citation-js/core": "^0.7.1",
"@citation-js/plugin-bibtex": "^0.7.2",
"@citation-js/plugin-csl": "^0.7.2",
"@floating-ui/dom": "^1.7.0",
"@fontsource/atkinson-hyperlegible": "^5.0.17",
"@fortawesome/fontawesome-free": "^5.15.4",
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.9",
"@guolao/vue-monaco-editor": "^1.5.4",
"@handsontable/vue": "^2.0.0",
"@hirez_io/observer-spy": "^2.1.2",
"@johmun/vue-tags-input": "^2.1.0",
"@monaco-editor/loader": "^1.5.0",
"@popperjs/core": "^2.11.8",
"@sentry/vue": "^10.45.0",
"@vueuse/core": "^10.5.0",
"@vueuse/math": "^10.9.0",
"@vueuse/shared": "^10.5.0",
"ace-builds": "^1.39.0",
"ag-grid-community": "^30",
"ag-grid-vue": "^30",
"assert": "^2.1.0",
"axios": "^1.6.2",
"bootstrap": "4.6",
"bootstrap-vue": "^2.23.0",
"csv-parse": "^5.5.2",
"d3": "^7.8.5",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"d3v3": "npm:d3@3",
"date-fns": "^2.30.0",
"decode-uri-component": "^0.2.1",
"dexie": "^3.2.5",
"diff": "^8.0.3",
"dom-to-image": "^2.6.0",
"dompurify": "^3.0.6",
"echarts": "^5.5.1",
"elkjs": "^0.8.2",
"events": "^3.3.0",
"file-saver": "^2.0.5",
"flush-promises": "^1.0.2",
"font-awesome-6": "npm:@fortawesome/free-solid-svg-icons@6",
"glob": "^12.0.0",
"handsontable": "^4.0.0",
"hsluv": "^1.0.1",
"imask": "^7.1.3",
"jquery": "2",
"jquery-migrate": "~1.4",
"jquery-mousewheel": "^3.1.13",
"jquery-ui": "^1.13.2",
"katex": "^0.16.22",
"linkify-html": "^4.1.1",
"linkifyjs": "^4.1.1",
"lodash": "^4.17.21",
"lodash.isequal": "^4.5.0",
"lucide-vue": "^0.344.0",
"markdown-it": "^14.1.1",
"markdown-it-regexp": "^0.4.0",
"monaco-editor": "^0.52.2",
"monaco-languageserver-types": "^0.4.0",
"monaco-types": "^0.1.0",
"monaco-worker-manager": "^2.0.1",
"monaco-yaml": "^5.4.0",
"object-hash": "^3.0.0",
"openapi-fetch": "^0.17.0",
"pinia": "^2.3.1",
"popper.js": "^1.16.1",
"pretty-bytes": "^6.1.1",
"pyre-to-regexp": "^0.0.6",
"querystring-es3": "^0.2.1",
"ro-crate-zip-explorer": "^0.4.1",
"rxjs": "^7.8.1",
"rxjs-spy": "^8.0.2",
"rxjs-spy-devtools-plugin": "^0.0.4",
"schema-to-ts": "^0.0.2",
"simplify-js": "^1.2.4",
"slugify": "^1.6.6",
"stream-browserify": "^3.0.0",
"timers-browserify": "^2.0.12",
"toastr": "^2.1.4",
"tus-js-client": "^3.1.1",
"util": "^0.12.5",
"vega": "^5.30.0",
"vega-embed": "^6.26.0",
"vega-lite": "^5.21.0",
"vscode-languageserver-types": "^3.17.5",
"vue": "^2.7.16",
"vue-class-component": "^7.2.6",
"vue-demi": "0.13.11",
"vue-echarts": "^7.0.3",
"vue-infinite-scroll": "^2.0.2",
"vue-json-pretty": "^1.9.5",
"vue-multiselect": "^2.1.7",
"vue-observe-visibility": "^1.0.0",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.6.5",
"vue-rx": "^6.2.0",
"vue-virtual-scroll-list": "^2.3.5",
"vue2-teleport": "^1.0.1",
"vuedraggable": "^2.24.3",
"xml-beautifier": "^0.5.0",
"yaml": "^2.6.1"
},
"scripts": {
"develop": "NODE_ENV=development vite",
"build": "NODE_ENV=development node scripts/build.mjs && vite build && pnpm run stage-build",
"build-production": "NODE_ENV=production node scripts/build.mjs && vite build && pnpm run stage-build",
"build-production-maps": "GXY_BUILD_SOURCEMAPS=1 NODE_ENV=production node scripts/build.mjs && vite build && pnpm run stage-build",
"icons": "node scripts/build.mjs icons",
"plugins": "node scripts/build.mjs plugins",
"plugins-rebuild": "node scripts/build.mjs plugins --force",
"stage-build": "cpy 'dist/**/*' '../static/dist' && (git rev-parse HEAD 2>/dev/null || echo '') >../static/client_build_hash.txt",
"format": "prettier --write . --cache",
"format-check": "prettier --check .",
"prettier": "pnpm run format",
"test": "vitest run",
"test:ui": "vitest --ui",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"eslint-precommit": "eslint -c .eslintrc.js --quiet --ext .js,.vue,.ts src",
"eslint": "eslint -c .eslintrc.js --ext .js,.vue,.ts src",
"type-check": "vue-tsc --noEmit"
},
"devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.1.1",
"@pinia/testing": "0.1.5",
"@rollup/plugin-inject": "^5.0.5",
"@testing-library/jest-dom": "^6.4.8",
"@types/d3": "^7.4.2",
"@types/d3-selection": "^3.0.11",
"@types/d3-zoom": "^3.0.8",
"@types/dompurify": "^3.0.2",
"@types/jquery": "^3.5.24",
"@types/lodash": "^4.14.200",
"@types/lodash.isequal": "^4.5.7",
"@types/markdown-it": "^13.0.4",
"@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^6.8.0",
"@vitejs/plugin-vue2": "^2.3.4",
"@vitest/coverage-v8": "^4.1.5",
"@vitest/spy": "^4.1.5",
"@vitest/ui": "^4.1.5",
"@vue/test-utils": "^1.3.6",
"@vue/tsconfig": "^0.4.0",
"autoprefixer": "10.4.16",
"buffer": "^6.0.3",
"cpy-cli": "^5.0.0",
"esbuild": "^0.27.4",
"eslint": "^8.52.0",
"eslint-plugin-compat": "^4.2.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-vue": "^9.17.0",
"eslint-plugin-vuejs-accessibility": "^2.2.0",
"fake-indexeddb": "^6.0.0",
"fs-extra": "^11.2.0",
"happy-dom": "^20.0.10",
"msw": "^2.3.4",
"openapi-msw": "^0.7.0",
"openapi-typescript": "^7.3.0",
"prettier": "^3.6.2",
"sass": "^1.94.2",
"store": "^2.0.12",
"timezone-mock": "^1.3.6",
"typescript": "^5.7.3",
"vite": "^8.0.11",
"vitest": "^4.1.5",
"vitest-fail-on-console": "^0.10.1",
"vitest-location-mock": "^1.0.4",
"vue-eslint-parser": "^10.2.0",
"vue-template-compiler": "^2.7.16",
"vue-tsc": "2.2.12",
"xml-js": "^1.6.11",
"xml2js": "^0.6.2"
},
"peerDependencies": {
"postcss": "^8.4.6"
}
}