Skip to content

docs: apply ecosystem documentation style consistency#5

Merged
joshsmithxrm merged 1 commit intomainfrom
feature/documentation-style-consistency
Dec 16, 2025
Merged

docs: apply ecosystem documentation style consistency#5
joshsmithxrm merged 1 commit intomainfrom
feature/documentation-style-consistency

Conversation

@joshsmithxrm
Copy link
Copy Markdown
Owner

Summary

  • Update CLAUDE.md to follow extension documentation conventions
  • Add emoji section headers (🚫 NEVER, ✅ ALWAYS, 💻 Tech Stack, etc.)
  • Add ✅/❌ patterns for code examples showing correct vs incorrect patterns
  • Add project-specific NEVER/ALWAYS rules for SDK development

Test plan

  • Review CLAUDE.md formatting renders correctly on GitHub
  • Verify emoji headers display properly
  • Confirm code examples are properly formatted

Update CLAUDE.md to follow extension documentation conventions:
- Add emoji section headers (🚫 NEVER, ✅ ALWAYS, 💻 Tech Stack, etc.)
- Add ✅/❌ patterns for code examples
- Reorganize sections for AI-first readability
- Add project-specific NEVER/ALWAYS rules
Copilot AI review requested due to automatic review settings December 16, 2025 19:40
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @joshsmithxrm, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on enhancing the CLAUDE.md documentation to improve its clarity, consistency, and utility for developers. By adopting ecosystem-wide conventions, introducing visual cues like emoji headers, and providing explicit code examples for best practices, the changes aim to streamline the onboarding and development process within the Power Platform ecosystem. The update ensures that critical development rules and guidelines are easily accessible and understandable.

Highlights

  • Documentation Style Consistency: The CLAUDE.md file has been updated to align with established extension documentation conventions, ensuring a standardized look and feel across the ecosystem.
  • Emoji Section Headers: New emoji-prefixed section headers (e.g., "🚫 NEVER", "✅ ALWAYS", "💻 Tech Stack") have been introduced to improve readability and visual organization of the document.
  • Code Example Patterns: The documentation now includes ✅ Correct and ❌ Wrong patterns for code examples, specifically demonstrating best practices for nullable reference types and XML documentation in C#.
  • Project-Specific Development Rules: Dedicated "🚫 NEVER" and "✅ ALWAYS" sections have been added to outline critical project-specific rules and guidelines for SDK development, covering aspects like strong naming, testing, and documentation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly improves the CLAUDE.md documentation by establishing clear conventions, such as emoji-prefixed headers, NEVER/ALWAYS rule tables, and / code examples. The reorganization makes the development guidelines more accessible and actionable for contributors. The changes are well-executed and align with the goal of documentation consistency. I have one suggestion to improve the portability of a file path reference.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the CLAUDE.md documentation file to follow ecosystem-wide documentation conventions by introducing emoji section headers, structured NEVER/ALWAYS rule tables, and code examples with visual correct/incorrect indicators.

Key Changes:

  • Added emoji-prefixed section headers (🚫 NEVER, ✅ ALWAYS, 💻 Tech Stack, etc.) for better visual organization
  • Introduced structured tables for prohibited and required practices with explanations
  • Added code examples using ✅/❌ prefixes to clearly distinguish correct from incorrect patterns
  • Reorganized content by moving Code Conventions section earlier in the workflow

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@joshsmithxrm joshsmithxrm merged commit 4ee6bd8 into main Dec 16, 2025
8 checks passed
@joshsmithxrm joshsmithxrm deleted the feature/documentation-style-consistency branch December 25, 2025 10:10
joshsmithxrm added a commit that referenced this pull request Jan 8, 2026
joshsmithxrm added a commit that referenced this pull request Jan 8, 2026
* docs: audit roadmap and create tracking for v0.3.0 feature completion

- Create FINISH_FEATURES_TODO.md tracking document for branch work
- Update DATA_MANAGEMENT.md: Data Explorer core marked implemented,
  IntelliSense/Aggregates/JOINs/Saved Queries as planned for v0.3.0
- Update DEVELOPMENT_TOOLS.md: Web Resources marked fully implemented,
  remaining items are conflict detection, JS validation, retry logic
- Update METADATA_BROWSER_ENHANCEMENTS.md: Core marked implemented,
  solution filtering and CSV export planned
- Update INFRASTRUCTURE.md: Review status confirmed
- Update future/README.md: Status table with implementation checkmarks

Scope for this branch:
- Web Resources: conflict detection, JS validation, retry logic (~6-9h)
- Metadata Browser: solution filtering, CSV export (~7-10h)
- Data Explorer: IntelliSense, Aggregates, JOINs, Saved Queries (~40-55h)

Deferred to ALM/Deployment Settings work:
- Local folder sync for Web Resources

* feat(web-resources): add conflict detection for concurrent edits

Warn users when saving a web resource that was modified by another user
since they opened it. Shows modal with options: Overwrite, Reload from
Server, or Cancel.

Changes:
- Add getModifiedOn() to IWebResourceRepository for lightweight timestamp check
- Extend WebResourceContentResult to include modifiedOn timestamp
- Store server's modifiedOn in FileSystemProvider cache
- Check for conflicts before each save, show warning modal if detected
- Refresh modifiedOn after successful save to track new baseline

Also documents bug: FileSystemProvider uses wrong connection when multiple
panels open with different auth contexts (to be fixed in next commit).

* fix(web-resources): add HTTP cache prevention to all API calls

- Add Cache-Control and Pragma headers to prevent HTTP caching
- Add cache: 'no-store' to all fetch() calls
- Fixed in: DataverseApiService (main + batch), WhoAmIService, PowerPlatformApiService
- Admin/developer tools must never show stale data

Also includes FileSystemProvider refactor from previous session:
- WebResourceConnectionRegistry for multi-environment support
- stat() optimization (no longer calls readFile)
- VS Code document caching workarounds

Note: 3 unit tests failing due to removed TTL caching behavior (tracked in docs)

* fix(web-resources): fetch unpublished content and fix reload from server

- Change getContent() to use RetrieveUnpublished bound function
  so developers see their latest saved changes (even before publish)
- Fix "Reload from Server" to properly update editor content
  by using WorkspaceEdit instead of unreliable revert command
- Add test coverage for reload from server conflict resolution

* feat(web-resources): detect unpublished changes on file open

When opening a web resource, compares published vs unpublished content.
If different, opens VS Code's merge editor to let user choose version.

Changes:
- Add getPublishedContent() to IWebResourceRepository
- Add mode support in FileSystemProvider URIs (published/unpublished/conflict)
- Open merge editor when unpublished changes detected
- Fetch both versions in parallel for comparison

* docs: add web resource version selection UX design

Documents two processes for handling version differences:
1. Opening files with unpublished changes (choose starting point)
2. Save conflicts when server modified (conflict resolution)

Both use "select one file" approach with diff view for comparison.
Merge editor rejected as overkill for typical use cases.

Design ready for implementation in next session.

* feat(web-resources): implement version selection UX for conflicts

Process 1 - Opening with unpublished changes:
- Show diff view (Published left, Unpublished right)
- Non-modal notification: "Edit Unpublished" / "Edit Published" / "Cancel"
- Close diff and open chosen version as editable file

Process 2 - Save conflict detection:
- Add "Compare First" option to conflict modal (reordered as primary)
- Show diff view (Server left, Local right) with non-modal buttons
- "Save My Version" / "Use Server Version" / "Cancel"

Technical implementation:
- Add 'server-current' and 'local-pending' content modes to URI scheme
- Add pendingSaveContent map for temporary storage during diff
- Non-modal notifications allow scrolling diff while deciding

* fix(web-resources): add syntax highlighting for opened files

VS Code doesn't auto-detect language mode for custom URI schemes.
Added setTextDocumentLanguage call after opening documents to enable
syntax highlighting based on file extension (.js, .ts, .css, .html, etc.)

* feat(web-resources): add Created By and Modified By columns

- Add createdBy/modifiedBy fields to WebResource entity
- Update repository to fetch user names via $expand on createdby/modifiedby
- Update ViewModel and mapper for table display
- Update tests across all layers

docs: update roadmap for v0.3.0
- Mark Web Resources and Metadata Browser as complete
- Move solution filtering to Solution Explorer (future)
- Defer Excel export (bundle size concern)
- Only Data Explorer remains (~40-55h)

* docs: clarify HTTP cache prevention is defensive measure, not bug fix

- Update comments in DataverseApiService to explain cache prevention
  is for admin tool reliability, not fixing a specific bug
- Update tracking doc to reflect that the original "stale content"
  issue was caused by Dataverse returning published content (fixed
  by RetrieveUnpublished), not HTTP caching
- Remove duplicate Decision Log section from tracking doc
- Add decision log entry documenting the clarification

* fix(panels): refresh solution dropdown on environment switch

When switching environments, the solution dropdown retained stale
solutions from the previous environment instead of loading solutions
for the new environment.

Root causes fixed:
- messaging.js had no handler for updateSolutionSelector message
- Connection References panel didn't call loadSolutions() on env change

Added regression tests to prevent future regressions.

* docs: add FileSystemProvider complexity to technical debt

- Add low-priority item for WebResourceFileSystemProvider (~800 lines)
- Document proposed solution: extract ConflictResolver and ContentModeHandler
- Trigger: when adding new content modes or conflict features
- Update README.md counts and file structure

* fix(web-resources): simplify conflict resolution reload flow

- Remove unnecessary waitForPendingFetch + notifyFileChanged + revert
  pattern from panel (VS Code fetches fresh content without it)
- Simplify reloadFromServer() to close editor and reopen with fresh
  unpublished URI (fixes bug where ?mode=published was being loaded)
- Remove unused waitForPendingFetch() and notifyFileChanged() methods
- Update tests to match new reload behavior

Note: There is still a timing issue where VS Code shows stale content
briefly before readFile() completes. This needs further investigation.

* docs: remove references to removed VS Code caching workaround

* feat(web-resources): add pre-fetch, publish prompt, and defensive logging

- Pre-fetch content before opening document in reloadFromServer()
  to eliminate flicker when choosing "Use Server Version"
- Add publish prompt when user opens file with unpublished changes
  and chooses "Edit Unpublished" version
- Add defensive logging for external content changes detected
  during VS Code auto-refresh
- Document expected VS Code "content is newer" dialog behavior
  after auto-refresh (VS Code's built-in protection, not a bug)

* fix(web-resources): use WorkspaceEdit to reload server content on conflict

The "Use Server Version" option during conflict resolution was not
showing the server content - VS Code cached the dirty document content
and didn't call readFile after revert.

Changes:
- Replace revert approach with WorkspaceEdit.replace() to directly
  update document buffer, bypassing VS Code's caching
- Remove redundant closeActiveEditor call that was prompting to save
- Use setImmediate to defer document.save() and avoid deadlock when
  clearing the dirty flag (nested writeFile calls)
- Store normalized document content (after VS Code applies edit) as
  lastKnownContent to ensure contentEquals matches on save

Add regression tests verifying WorkspaceEdit is used for reload.

* fix(panels): show loading immediately on environment switch

Clear stale data and show loading indicator at the START of
handleEnvironmentChange(), before async operations begin.

Previously, users saw ~1 second of stale data from the previous
environment while solutions and metadata were being fetched.

* fix(panels): show loading placeholder in solution dropdown on env switch

When switching environments, immediately show "Loading solutions..."
placeholder and disable the dropdown to prevent selecting stale
solutions from the previous environment.

The dropdown re-enables when actual solutions are loaded.

* docs: update CHANGELOG with unreleased features and fixes

Add entries for Web Resources conflict detection, unpublished changes
detection, Created By/Modified By columns, environment switch loading
state improvements, solution dropdown refresh fix, and syntax highlighting.

* fix(panels): implement SafeWebviewPanel and two-level cancellation

Problem:
- Closing panels during long-running API requests caused "Webview is disposed" errors
- API requests continued running after panel closure, wasting server resources
- Paginated fetches (5000+ records per page) kept making requests after disposal

Solution:
- SafeWebviewPanel wrapper that tracks disposal and provides safe postMessage()
- ISafePanel interface for behaviors to use safe messaging
- AbortSignalCancellationTokenAdapter to convert panel abort signal to cancellation token
- CompositeCancellationToken to combine panel-level and operation-level cancellation
- Two-level cancellation in WebResourcesPanelComposed:
  - Level 1: Panel closes → all operations cancelled via abortSignal
  - Level 2: User changes solution/environment → previous operation cancelled

Files added:
- SafeWebviewPanel.ts - wrapper with disposal tracking and safe postMessage
- ISafePanel.ts - interface for safe panel operations
- AbortSignalCancellationTokenAdapter.ts - adapts AbortSignal to ICancellationToken
- CompositeCancellationToken.ts - combines multiple cancellation tokens

Updated:
- All 11 panel classes to use SafeWebviewPanel
- All behavior classes to use ISafePanel
- PanelCoordinator to use SafeWebviewPanel
- WebResourcesPanelComposed with full two-level cancellation

* docs: add panel disposal cancellation fix to CHANGELOG

* docs: add Data Explorer v0.3.0 requirements and update design

Requirements (DATA_EXPLORER_V03_REQUIREMENTS.md):
- 5 phases: IntelliSense, Query History, Aggregates/JOINs, INSERT/UPDATE/DELETE, Visual Query Builder
- Design decisions: hybrid history UI, batch size 100, personal views only
- Current architecture documented (SQL ↔ FetchXML bidirectional modes)
- Three-mode architecture for Phase 5 (SQL | FetchXML | Visual)
- Estimated effort: 62-90 hours

IntelliSense Design (V2.1 updates):
- Added SqlEditorWatcher for panel-editor sync
- Added Ctrl+Enter keybinding for query execution
- Clarified two-mode architecture (SQL in VS Code, FetchXML in panel)
- Added future phase keywords (INSERT, UPDATE, DELETE, aggregates)
- Added future phase considerations section

Progress Tracking:
- Updated Data Explorer section with 5-phase breakdown
- Added 7 new design decisions to Decision Log
- Added session progress for 2025-12-04

* feat(data-explorer): implement IntelliSense core components

Phase 1 of Data Explorer IntelliSense implementation:

Domain Layer:
- EntitySuggestion, AttributeSuggestion value objects
- SqlContextDetector domain service (detects completion context)
- IIntelliSenseMetadataRepository interface

Application Layer:
- IntelliSenseContextService (tracks active environment)
- IntelliSenseMetadataCache (caches metadata with TTL)
- GetEntitySuggestionsUseCase, GetAttributeSuggestionsUseCase

Infrastructure Layer:
- DataverseIntelliSenseMetadataRepository (OData API calls)

Presentation Layer:
- DataverseCompletionProvider (VS Code completion provider)
- SqlEditorService (opens new SQL queries and files)
- SqlEditorWatcher (watches editor changes for sync)

Tests:
- Full test coverage for domain and application layers
- All 494 Data Explorer tests pass

Remaining: Extension registration, panel integration, Ctrl+Enter keybinding

* feat(data-explorer): complete Phase 1 IntelliSense integration

- Register completion provider for ALL SQL files (singleton pattern)
- Add New Query and Open File buttons to panel toolbar
- Wire IntelliSenseContextService to panel environment changes
- Add Ctrl+Enter keybinding for query execution from VS Code editor
- Connect SqlEditorWatcher for FetchXML preview sync with external editor
- Add query execution request events to IntelliSenseContextService

Phase 1 of Data Explorer IntelliSense is now complete:
- Entity/attribute completions work in native VS Code SQL files
- Panel integrates with external SQL editor for FetchXML preview
- Users can execute queries from VS Code with Ctrl+Enter

* fix(data-explorer): improve IntelliSense context detection for partial typing

The SqlContextDetector was only detecting attribute context when cursor
was immediately after keywords (SELECT, WHERE, etc.) with trailing whitespace.
This caused completions to stop working as soon as the user started typing.

Changed from exact position matching to REGION-based detection:
- SELECT column list region (between SELECT and FROM)
- WHERE clause region (after WHERE/AND/OR, before operators)
- ORDER BY region (after ORDER BY)

Now completions work correctly when:
- Typing partial attribute name: "SELECT na|me FROM account"
- Editing after writing full query: go back to modify columns
- Adding columns after comma: "SELECT name, acc| FROM account"

Added 7 new tests for partial typing scenarios.

* feat(data-explorer): add Dataverse SQL Notebooks POC

Implement VS Code notebook support for SQL queries against Dataverse:

- Add .dataverse-sql notebook file format with custom serializer
- Create notebook controller for cell execution
- Render results as theme-aware HTML tables using VS Code CSS variables
- Environment selection via status bar picker
- Persist environment in notebook metadata
- Add "New Dataverse SQL Notebook" command

Known limitation: IntelliSense uses global environment context, not
per-notebook. Tracked in docs/work/DATAVERSE_SQL_NOTEBOOKS_TODO.md.

* feat(data-explorer): integrate notebooks with Data Explorer panel

- Add "Open in Notebook" button to Data Explorer toolbar
- Export openQueryInNotebook function for panel integration
- Notebook inherits environment and SQL from panel
- Pre-populate notebook with panel's current query

* style(notebooks): match Data Explorer table styling

Update notebook results HTML to match Data Explorer panel:
- Blue header background (--vscode-button-background)
- Alternating row colors for readability
- Consistent cell padding and borders
- Status bar with row count and execution time
- Link styling matching panel
- Sticky header for scrolling

* fix(notebooks): auto-size table columns to content

* fix(notebooks): remove special styling for primary key column

* feat(notebooks): add clickable links and rename to .ppdsnb

- Add clickable links for lookup fields (opens record in browser)
- Add clickable links for primary key columns (e.g., accountid)
- Store environment URL in notebook metadata for link generation
- Rename notebook extension from .dataverse-sql to .ppdsnb
- Update display name to "Power Platform Developer Suite Notebook"

* feat(notebooks): add FetchXML cell support with custom language

- Support both SQL and FetchXML cells in notebooks
- Register custom 'fetchxml' language with XML syntax highlighting
- Language picker now shows "FetchXML" instead of generic "XML"
- Detect cell language and route to appropriate use case:
  - SQL cells: transpile to FetchXML then execute
  - FetchXML cells: execute directly
- Add content-based detection for cells starting with <fetch
- Preserve cell language in serializer (sql/fetchxml/markdown)

* feat(intellisense): add FetchXML IntelliSense support

Implement context-aware IntelliSense for FetchXML queries:
- Element suggestions based on parent element hierarchy
- Attribute name suggestions for each element type
- Attribute value suggestions (entity names, attribute names, operators)
- 70+ condition operators with descriptions and categories
- 41 unit tests for context detection

* refactor(notebooks): rename SqlNotebook to Notebook and add auto-switch

- Rename DataverseSqlNotebookController → DataverseNotebookController
- Rename DataverseSqlNotebookSerializer → DataverseNotebookSerializer
- Rename registerDataverseSqlNotebooks → registerDataverseNotebooks
- Rename command newDataverseSqlNotebook → newDataverseNotebook
- Add auto-switch cell language based on content:
  - First char '<' → FetchXML mode
  - First char not '<' → SQL mode
  - Only triggers for cells < 30 chars (fresh cells)
- Update openQueryInNotebook to auto-detect SQL vs FetchXML
- Keep user-facing name as "Power Platform Developer Suite Notebook"
- Add visual builder implementation plan

* fix: resolve 5 bugs across multiple panels

Bug #1: Persistence Inspector text selection
- Move user-select: none from .tree-header to .tree-toggle only

Bug #2: Plugin Trace Viewer Ctrl+A shortcuts
- Add keyboard shortcut handlers with stopPropagation for inputs

Bug #3: Import Job Viewer clickable links
- Use CellLink pattern instead of HTML string in mapper
- VirtualTableRenderer expects solutionNameLink, not solutionNameHtml

Bug #4: Infinite loading spinner (5 panels)
- Replace scaffoldingBehavior.refresh with postMessage for data updates
- Move showTableLoading before data fetch

Bug #5: Data Explorer aliased lookup links
- Detect lookuplogicalname annotation on any key, not just _xxx_value

* fix(persistence-inspector): enable text selection for content elements

Add user-select: text to entry and tree view elements to allow mouse
selection and Ctrl+A within storage entries. Buttons remain unselectable
to prevent copying UI elements with content.

* fix(tests): add flushPromises helper for async panel handler tests

PanelCoordinator uses `void this.handleMessage()` which runs handlers
asynchronously without awaiting. Tests were failing because they
asserted before async operations completed.

Changes:
- Add flushPromises() helper to 4 test files
- Update message handler calls to not await (returns void)
- Add flushPromises() after handler calls to wait for async completion
- Add longer delays in WebResources race condition tests (150ms, 100ms)
  to wait for mock setTimeout delays to complete

Affected test files:
- PersistenceInspectorPanelComposed.integration.test.ts
- EnvironmentSetupPanelComposed.integration.test.ts
- WebResourcesPanelComposed.integration.test.ts
- MetadataBrowserPanel.integration.test.ts

* docs: clean up tracking docs, add aggregate/DISTINCT scope

- Delete completed tracking docs:
  - BUG_FIXES_2025_12_TODO.md (all 5 bugs fixed)
  - DATAVERSE_SQL_NOTEBOOKS_TODO.md (all issues resolved)
  - SAFE_WEBVIEW_PANEL_CANCELLATION_TODO.md (moved to tech debt)

- Add panel cancellation pattern to low-priority tech debt
  - Documents the pattern for future reference
  - Lists panels that could benefit from cancellation
  - WebResources implementation serves as reference

- Update INTELLISENSE_IMPROVEMENTS_TODO.md:
  - Mark Phase 3 (notebooks) as COMPLETED
  - Add detailed Phase 4 scope: DISTINCT, COUNT, SUM, AVG, MIN, MAX, GROUP BY
  - Add future phases for advanced features (HAVING, date grouping, exotic JOINs)
  - Document FetchXML link-types that have no SQL equivalent

* feat(sql): add DISTINCT, aggregate functions, and GROUP BY support

Add SQL features that have direct FetchXML equivalents:

Parser:
- DISTINCT keyword: SELECT DISTINCT name FROM account
- COUNT(*), COUNT(column), COUNT(DISTINCT column)
- SUM, AVG, MIN, MAX aggregate functions
- GROUP BY clause with multiple columns
- Aliases for aggregates: COUNT(*) AS total

Transpiler:
- DISTINCT → <fetch distinct="true">
- Aggregates → <fetch aggregate="true"> with appropriate aggregate type
- COUNT(*) uses entity primary key column (e.g., accountid)
- GROUP BY → groupby="true" on attribute elements
- Auto-generate aliases for aggregates without explicit alias

Fixes:
- Preserve original casing in lexer for keyword aliases
- COUNT(*) uses {entityname}id instead of invalid name="*"

Tests: 39 new unit tests (20 parser + 19 transpiler)

* feat(notebooks): add SQL/FetchXML toggle conversion and improve paste detection

Toggle button now converts query content when switching languages:
- SQL → FetchXML: Uses SqlParser + SqlToFetchXmlTranspiler
- FetchXML → SQL: Uses FetchXmlToSqlTranspiler with full aggregate support

FetchXmlToSqlTranspiler now properly handles:
- COUNT(*), COUNT(column), SUM, AVG, MIN, MAX aggregate functions
- COUNT(DISTINCT column) for distinct aggregates
- GROUP BY clause from groupby="true" attributes
- DISTINCT keyword from fetch distinct="true"

Paste auto-detection improved:
- Now detects paste anywhere in cell, not just empty cells
- Switches language based on pasted content's first character
- '<' triggers FetchXML, anything else triggers SQL (if in FetchXML mode)

* feat(sql): preserve comment positions during SQL/FetchXML round-trip

- Add trailing comment support for conditions (comparison, LIKE, NULL, IN)
- Fix parser to attach comments after commas in column/ORDER BY/GROUP BY lists
- Generate multi-line SQL output for better readability
- Associate XML comments with nearest preceding element by position
- Output inline comments on correct lines instead of dumping at top

SQL output now formatted as:
  -- Leading comment
  SELECT
    column1, -- comment
    column2
  FROM table
  WHERE condition = value -- inline comment

* fix(fetchxml): support ORDER BY alias in aggregate queries and XML comments

- FetchXmlValidator: Add context-aware order validation
  - Aggregate queries require 'alias', regular queries require 'attribute'
  - Add XML comment stripping to handle comments before <fetch>

- FetchXmlToSqlTranspiler: Parse and use 'alias' for ORDER BY
  - Update ParsedOrder interface to include optional alias field
  - Use alias in ORDER BY clause when present (aggregate queries)

- SqlToFetchXmlTranspiler: Generate alias for ORDER BY in aggregates
  - Detect when ORDER BY column matches a SELECT alias
  - Preserve alias case when generating <order alias="...">

Fixes validation error for Microsoft's documented aggregate FetchXML syntax.

* docs: adopt stabilization-first testing workflow

Change testing philosophy from per-layer tests during implementation
to tests after F5 validation. This reduces wasted effort when pivoting
and gets to manual testing faster.

Key changes:
- Phase 5: Implementation now "exploration mode" (no tests required)
- Phase 6: New "F5 Validation" phase for manual testing/iteration
- Phase 7: New "Stabilization & Tests" phase (required before PR)
- Coverage targets changed to guidelines (80%+ domain, 70%+ app)
- Tests still required before PR, just written after design is validated

* docs: consolidate tracking docs and update feature status

- Update DATA_MANAGEMENT.md: move IntelliSense, Notebooks, Aggregates,
  and basic JOINs to Implemented section; add Deferred section for
  advanced features (HAVING, date grouping, exotic JOINs)
- Delete INTELLISENSE_IMPROVEMENTS_TODO.md (completed)
- Simplify FINISH_FEATURES_TODO.md to reflect actual progress
- Update docs/future/README.md with current status

* feat(data-explorer): implement Visual Query Builder with entity selection

Visual Query Builder Step 2 implementation:
- Entity picker dropdown with grouped standard/custom entities
- Collapsible query preview section (FetchXML/SQL tabs)
- Results table with search, sorting, and record links
- Syntax highlighting using shared SqlHighlighter/XmlHighlighter

Domain layer:
- VisualQuery value object with column/filter/sort management
- QueryColumn, QueryCondition, QueryFilterGroup, QuerySort value objects
- FetchXmlOperator with comprehensive operator-to-FetchXML mapping
- FetchXmlGenerator and FetchXmlParser services

Bug fixes in this commit:
- Fixed entity selection sending null (message format mismatch)
- Fixed results table not rendering (missing message handlers)
- Fixed preview tab warnings (added data-custom-handler attribute)
- Eliminated syntax highlighting duplication via webpack bundling

* fix(panels): resolve loading state race condition showing "No data found" flash

- Add isLoading: true to scaffold refresh in all 6 data table panels
- Fix virtualTableSectionView to render loading row inside table structure
- Fix dataTableSectionView to render loading row inside table structure
- Add data-loading attribute for VirtualTableRenderer to detect loading state
- Update VirtualTableRenderer to skip initial render when loading
- Fix solutionFilterView to always render container for postMessage updates
- Update PANEL_INITIALIZATION_PATTERN.md with correct patterns

Affected panels: Import Jobs, Solutions, Web Resources, Environment Variables,
Connection References, Plugin Traces

* docs: add environment repository caching to technical debt

Track redundant EnvironmentRepository.getAll() calls during panel
initialization. 5+ calls happen within 100ms, each mapping all DTOs.
Low priority - fix when naturally touching EnvironmentRepository.

* refactor(notebook): simplify naming and add FetchXML example

- Rename command from "New Dataverse Notebook" to "New Notebook"
- Rename displayName from "Power Platform Developer Suite Notebook" to "Dataverse Notebook"
- Add FetchXML example cell to default notebook content (alongside SQL)
- Add tip about toggle button for SQL/FetchXML conversion

* fix(panels): add openMaker to LoadingStateBehavior config in all 6 panels

When scaffold renders with isLoading: true, ActionButtonsSection disables
ALL buttons in HTML. LoadingStateBehavior.setLoading(false) only re-enables
buttons in its config. Since openMaker was excluded, it stayed disabled.

Changes:
- Add openMaker to LoadingStateBehavior config in all 6 panels
- Fix SolutionExplorer showTableLoading to use updateVirtualTable command
- Add setLoading(true/false) wrapper in PluginTraceViewer initializeAndLoadData

Affected panels: Solutions, Import Jobs, Web Resources, Connection References,
Environment Variables, Plugin Traces

* feat(data-explorer): complete Visual Query Builder layout and UX improvements

- Implement two-pane scrollable layout with collapsible query builder section
- Fix columns not loading on panel restore (load attributes for restored entity)
- Fix Ctrl+A not working in search inputs (allow native behavior in text inputs)
- Persist and restore full query state (entity + selected columns)
- Use consistent search box pattern with emoji placeholder (🔍)
- Add ColumnOptionViewModel and mapper for column picker
- Update CSS for proper scrolling (max-height: 350px on query builder container)

* docs: update Visual Query Builder progress tracking

* feat(data-explorer): complete Filter Builder MVP with persistence and polish

Filter Builder Implementation:
- Add/remove filter conditions with field, operator, value inputs
- Operator dropdown varies by attribute type (text, number, date, lookup, etc.)
- Value input varies by type (text, number, datetime-local, boolean select)
- AND logic for all conditions (MVP scope)
- Filter persistence with entity and column state
- Filters appear in FetchXML/SQL preview in real-time
- Fix focus loss bug when typing in filter value inputs

Column Display & Ordering:
- Sort columns by logical name (not display name) for consistency
- Column picker: show "logicalName DisplayName Type" format
- Filter dropdown: show "logicalName (DisplayName)" format
- Update CSS to make logical name prominent (monospace font)
- Plugin Trace Viewer: sort filter fields by OData name

Bug Fixes:
- Filter out IsValidForRead=false columns to prevent query errors
- Virtual/computed columns like "isprivate" no longer cause API errors

Files added:
- FilterConditionViewModel.ts - ViewModel for filter row data
- FilterOperatorConfiguration.ts - Operators by attribute type

Files modified:
- DataExplorerPanelComposed.ts - Filter command handlers & persistence
- VisualQueryBuilderBehavior.js - Dynamic filter row rendering
- visualQueryBuilderView.ts - Server-side filter section HTML
- data-explorer.css - Filter section styling
- DataverseIntelliSenseMetadataRepository.ts - IsValidForRead filter
- ColumnOptionViewModelMapper.ts - Sort by logical name
- FilterField.ts - Sort Plugin Trace fields by OData name

* feat(data-explorer): complete Sort/Options sections + Cell Selection behavior

Visual Query Builder:
- Sort Section (3.4): attribute dropdown, direction toggle, clear button
- Query Options (3.5): Top N input (1-5000), Distinct checkbox
- Both sections collapsible with persistence and preview integration

Cell Selection (Excel-style):
- Created CellSelectionBehavior.js for cell-based rectangular selection
- Click to select, drag to range, Shift+click to extend
- Ctrl+A selects all cells, Ctrl+C copies as TSV
- Headers included in copy when entire table is selected
- Integrated with VirtualTableRenderer and DataTableBehavior
- Added to all 8 panels (works on 7, Data Explorer has timing issue)

Note: Data Explorer cell selection deferred - webpack bundle timing issue
with CellSelectionBehavior.js. Tracked in CELL_SELECTION_TODO.md.

* fix(plugin-traces): remove non-functional "Open in Maker" button

Plugin trace logs are not viewable in Maker Portal - they exist in
Admin Center or classic Dynamics. The button was incorrectly navigating
to the generic Maker home page which was not useful.

Removed:
- openMaker command type
- Button from toolbar (now just Refresh)
- Command handler and handleOpenMaker method
- LoadingStateBehavior config for the button

* fix(panels): fix environment reversion bug after switching and returning

The disposal handler was capturing the original environment ID at panel
creation time instead of using the current environment ID. When a user
switched environments within a panel, the disposal closure still had
the old ID, causing it to delete the wrong map entry and leave the new
environment's entry orphaned.

Added abstract getCurrentEnvironmentId() method to EnvironmentScopedPanel
that subclasses implement, allowing the disposal handler to use the
current environment ID at disposal time rather than the captured one.

Includes regression test that fails without fix and passes with fix.

* feat(data-explorer): add sticky Execute/Clear action bar to query builder

Add a sticky action bar at the bottom of the query builder section with
Execute and Clear buttons, always visible without scrolling.

- Add Execute button with loading spinner state and Ctrl+Enter shortcut
- Add Clear button that resets columns, filters, sort, options, results
  while preserving entity selection
- Move Execute from toolbar to action bar for better UX
- Style action bar with sticky positioning and VS Code theme colors

* docs: update progress tracking and defer features to future

Progress Updates:
- Mark Step 4 (Sticky Action Bar) as complete
- Mark Step 5 (Export/Import Toolbar) as in progress
- Reduce v0.3.0 scope to ~10-16 hours remaining

Deferred to v1.0+ (docs/future/DATA_MANAGEMENT.md):
- Query History (notebooks serve this purpose now)
- INSERT/UPDATE/DELETE
- View Management / UserQuery Save (needs layoutxml)
- Advanced VQB Features (AND/OR groups, Joins, Aggregates in VQB)

* docs: comprehensive changelog update for v0.3.0 and cleanup tracking docs

CHANGELOG additions:
- Data Explorer - Visual Query Builder (entity, columns, filters, sort, options)
- Data Explorer - Export/Import Toolbar (CSV, JSON, FetchXML, SQL, Notebook)
- Data Explorer - Notebook Integration (CodeLens, bidirectional workflow)
- Data Explorer - IntelliSense (SQL + FetchXML autocomplete)
- Data Explorer - Notebooks (.ppdsnb format)
- Data Explorer - Aggregates & JOINs (COUNT, GROUP BY, INNER/LEFT JOIN)
- Cell Selection (Excel-style) - all 8 panels

Deleted obsolete tracking docs:
- CELL_SELECTION_TODO.md (complete)
- DATA_EXPLORER_V03_REQUIREMENTS.md (superseded by FINISH_FEATURES_TODO.md)

* feat(data-explorer): add Export/Import toolbar with file export capabilities

- Move Execute/Clear buttons from sticky action bar to toolbar
- Add Export dropdown with Results (CSV, JSON) and Query (FetchXML, SQL, Notebook) options
- Add Import dropdown for FetchXML and SQL file import
- Import parses files and populates Visual Query Builder
- Fix file extension handling - ensure .csv, .json, .xml, .sql extensions are appended
- Fix notebook displayName consistency ("Power Platform Developer Suite Notebook")
- Remove old sticky action bar (actions now in toolbar)
- Reuse shared DropdownComponent for toolbar dropdowns

* fix(notebooks): rename Dataverse Notebook to Power Platform Developer Suite Notebook

Update all references to use the correct product name for consistency
with package.json displayName and other documentation.

* feat(notebooks): add bidirectional notebook-panel integration and cell export

Notebook → Panel Integration:
- Add "Open in Data Explorer" toolbar button for notebook code cells
- Parse cell query (SQL or FetchXML) and load into Visual Query Builder
- Environment from notebook metadata auto-selects panel environment

Notebook Cell Export:
- Add "Export Results to CSV" and "Export Results to JSON" in cell menu
- Store query results per cell after execution
- Reuse CsvExportService for file save dialog and export

Technical changes:
- DataExplorerPanelComposed: add loadQueryFromExternal() public method
- initializeDataExplorer: return panel instance for external interaction
- DataverseNotebookController: store results by cell URI, add export helpers
- package.json: new commands in notebook/cell/title menu

* refactor: remove pre-existing dead code from v0.2.0

Remove unused code that was created during the v0.2.0 Clean Architecture
refactor but never adopted:

HTML component helpers (never imported):
- button.ts, formField.ts, section.ts, select.ts
- Views use plain template literals instead of these abstractions

DataTablePanelCoordinator infrastructure (replaced by PanelCoordinator):
- DataTablePanelCoordinator.ts and its test
- IDataTablePanelCoordinator.ts interface
- DataTableBehaviorRegistry.ts and interface
- All panels use the newer PanelCoordinator pattern

Other dead code:
- pluginTraceToolbarView.ts - render function never imported
- isEnvironmentChangeMessage type guard - never called

* test(data-explorer): improve test coverage and document parser limitation

- Add 200+ tests across dataExplorer domain/application layers
- Exclude FetchXmlParser.ts and FetchXmlToSqlTranspiler.ts from coverage
  (regex-based parsing cannot handle nested XML - documented limitation)
- Document stack-based parser refactoring as scheduled technical debt
- All 289 test suites pass with 7,757 tests

Coverage improvements:
- SqlContextDetector: 79.7% → 95.94% stmts, 71.4% → 94.28% branches
- FetchXmlContextDetector: 87.5% → 92.18% branches
- IntelliSenseContextService: 69.6% → 100% stmts/branches
- SqlAst: 93.7% → 100% stmts/branches
- FetchXmlOperator: 75% → 100% branches
- QueryFilterGroup: 84.61% → 96.15% branches

Technical debt documented:
- docs/technical-debt/scheduled/PARSER_REFACTORING.md
- Solution: Stack-based tokenizer (zero dependencies)
- Timeline: Post v0.3.0 release

* refactor: remove remaining dead code helpers

Remove unused helper functions and files:
- ImportJobLinkView.ts - clickable link helpers never integrated
- SolutionLinkView.ts - clickable link helpers never integrated
- TypeGuards.ts - type guards never called (isTreeNodeArray, etc.)
- getInputTypeForAttribute - filter input helper never used

* docs: mark all v0.3.0 features complete

* docs: move Visual Query Builder to implemented in future planning

* refactor(data-explorer): extract coordinators from panel

Extract three coordinators from DataExplorerPanelComposed to separate
concerns and reduce file size (2182 → 1016 lines):

- DataExplorerMetadataLoader: entity/attribute loading and caching
- DataExplorerExportImportCoordinator: export/import file operations
- VisualQueryBuilderCoordinator: query state (columns, filters, sort)

Panel remains orchestrator, delegating via typed context interfaces.

* refactor(data-explorer): add eslint-disable justification for panel size

* docs: remove tracking docs (preserved in git history)

* chore(release): bump version to 0.3.0

Mark v0.3.0 release with all features complete:
- Web Resources with conflict detection
- Metadata Browser enhancements
- Data Explorer Visual Query Builder
- IntelliSense for SQL and FetchXML
- Aggregates & JOINs support
- Cell Selection (Excel-style)
- Notebook integration

* fix(deps): update jws to fix HMAC signature vulnerability

* fix(security): address CodeQL security findings

- Fix XSS vulnerabilities in VisualQueryBuilderBehavior.js:
  - Escape position text in showQueryError
  - Use DOM methods instead of innerHTML in updateQueryOptionsSummary
  - Validate boolean type for aria-selected attribute in updateColumnPicker
- Add origin verification to postMessage handler for VS Code webview security
- Fix incomplete XML comment sanitization in FetchXmlValidator.ts
  - Use iterative replacement to handle nested patterns
- Remove unused variable (searchInput) and function (clearColumnSearch)

* fix(security): escape position values individually for CodeQL

* test(data-explorer): add tests for IntelliSense value objects and use cases

Add comprehensive tests for:
- OperatorSuggestion value object (domain layer)
- ColumnOptionViewModelMapper (application mapper)
- GetFetchXmlElementSuggestionsUseCase
- GetOperatorSuggestionsUseCase
- IntelliSenseMetadataCache service

These tests address coverage threshold failures by achieving:
- 100% coverage for domain value objects (95% required)
- 100% coverage for application mappers and use cases (90% required)
- 87.5% branch coverage for cache service (85% required)

* test(coverage): add tests for Views and CompletionMappers

- Add tests for visualQueryBuilderView.ts (HTML generation)
- Add tests for all 4 CompletionMapper files (VS Code type mapping)
- Remove corner-cut exclusions from jest.config.js
- Coverage now legitimately meets thresholds:
  - Statements: 94.6% (threshold: 85%)
  - Branches: 91.1% (threshold: 80%)
  - Functions: 97.03% (threshold: 85%)
  - Lines: 94.61% (threshold: 85%)

* docs: restructure README for user focus

- Add Data Explorer and Web Resources to Key Features
- Add Quick Start, Settings, and Requirements sections
- Remove developer-focused content (architecture, test counts, coverage)
- Simplify installation (marketplace only)
- Add screenshots to technical debt for future work

Developer content already exists in CONTRIBUTING.md.
joshsmithxrm added a commit that referenced this pull request Mar 18, 2026
Solutions (#1,#2,#3,#5): Include Managed toggle, sort controls,
Visible/API Managed fields, Maker Portal button, isVisible/isApiManaged
end-to-end C# support.

Import Jobs (#7,#8,#9): Search bar, Operation Context column
(end-to-end C#→TS), record count with filtered status.

Plugin Traces (#11,#12,#17,#18,#55): CRITICAL Trace Level dropdown
fix, Maker Portal URL fix (Dynamics 365 classic), status text labels,
record count, search bar.

Web Resources (#19): Restored Created By and Created On columns.

Connection References (#23,#24,#25,#28,#55): Expandable flow/connection
detail with chevron toggle, ISO timestamp formatting, search bar,
status badge improvements (Unknown/Unbound), Sync Settings button.

Environment Variables (#29,#31,#55): Modified On column restored,
search bar, Sync Settings button.

Metadata Browser (#37): Custom Only filter toggle for entities.

Data Explorer (#41,#42): Clear button, Import button (file dialog).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
joshsmithxrm added a commit that referenced this pull request Mar 19, 2026
Solutions (#1,#2,#3,#5): Include Managed toggle, sort controls,
Visible/API Managed fields, Maker Portal button, isVisible/isApiManaged
end-to-end C# support.

Import Jobs (#7,#8,#9): Search bar, Operation Context column
(end-to-end C#→TS), record count with filtered status.

Plugin Traces (#11,#12,#17,#18,#55): CRITICAL Trace Level dropdown
fix, Maker Portal URL fix (Dynamics 365 classic), status text labels,
record count, search bar.

Web Resources (#19): Restored Created By and Created On columns.

Connection References (#23,#24,#25,#28,#55): Expandable flow/connection
detail with chevron toggle, ISO timestamp formatting, search bar,
status badge improvements (Unknown/Unbound), Sync Settings button.

Environment Variables (#29,#31,#55): Modified On column restored,
search bar, Sync Settings button.

Metadata Browser (#37): Custom Only filter toggle for entities.

Data Explorer (#41,#42): Clear button, Import button (file dialog).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
joshsmithxrm added a commit that referenced this pull request Mar 19, 2026
* fix(cdp): use pwsh Expand-Archive for VSIX extraction on Windows

bsdtar interprets the C: drive prefix as a remote host, breaking
VSIX extraction in --vsix mode. Use PowerShell's Expand-Archive on
Windows; keep tar on other platforms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* plan: extension UX audit fixes — 28 findings across 10 phases

Side-by-side comparison of legacy v0.3.4 vs new extension produced
55 findings. After triage: 28 fixes, 22 keep-new-behavior, 5 deferred.
Organized into 10 phases (1 cross-cutting + 9 per-panel) for parallel
agent execution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(ext): Phase 1 — cross-cutting infrastructure fixes

- Add Connection References + Environment Variables to sidebar Tools (#56, #57)
- Standardize command titles: "Open Data Explorer", "Open Plugin Traces" (#51)
- Remove text-transform: uppercase from 5 panel CSS files for Title Case headers (#10)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(ext): Phases 2-9 — UX audit fixes across all 8 panels

Solutions (#1,#2,#3,#5): Include Managed toggle, sort controls,
Visible/API Managed fields, Maker Portal button, isVisible/isApiManaged
end-to-end C# support.

Import Jobs (#7,#8,#9): Search bar, Operation Context column
(end-to-end C#→TS), record count with filtered status.

Plugin Traces (#11,#12,#17,#18,#55): CRITICAL Trace Level dropdown
fix, Maker Portal URL fix (Dynamics 365 classic), status text labels,
record count, search bar.

Web Resources (#19): Restored Created By and Created On columns.

Connection References (#23,#24,#25,#28,#55): Expandable flow/connection
detail with chevron toggle, ISO timestamp formatting, search bar,
status badge improvements (Unknown/Unbound), Sync Settings button.

Environment Variables (#29,#31,#55): Modified On column restored,
search bar, Sync Settings button.

Metadata Browser (#37): Custom Only filter toggle for entities.

Data Explorer (#41,#42): Clear button, Import button (file dialog).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ext): review fixes — colSpan bug and command injection

- Connection References: fix colCount from wrong heuristic (8) to
  correct value (7) matching actual column count
- CDP tool: sanitize paths for PowerShell injection (escape single
  quotes); use execFileSync for tar on Unix to avoid shell interpretation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(ext): extract inline styles to CSS classes per Gemini review

Move search input and sort select inline styles to dedicated
.toolbar-search and .toolbar-select CSS classes across 4 panels
(Connection Refs, Env Variables, Import Jobs, Solutions).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ext): add missing webview entry points to knip config

The 6 non-query/solutions webview panels are esbuild entry points but
were missing from knip.json, causing false-positive unused-file reports.
Also un-export internal-only types from shared modules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ext): solution filter GUID bug and raw ISO timestamps

- SolutionFilter shared component used uniqueName as option value
  instead of GUID id, causing "solutionId must be a valid GUID" error
  on Web Resources (and silently wrong on Conn Refs / Env Vars)
- Import Jobs and Web Resources rendered raw ISO timestamps; add
  formatDateTime helper matching Plugin Traces / Conn Refs pattern

Found by QA blind verification agents.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ext): review fixes — escaping, shell safety, URL normalization

- Solutions panel: escape rootComponentBehavior and boolean ternaries
  before innerHTML insertion (S1 compliance)
- Connection References: escape literal "Unbound" for consistent
  escaping discipline
- CDP tool: use execFileSync instead of execSync for PowerShell
  VSIX extraction (S2 compliance — no shell: true)
- Plugin Traces: strip trailing slash from environment URL before
  Maker Portal link construction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ext): escape formatDate output in solutions detail card

Wrap formatDate() calls in escapeHtml() for consistent escaping
discipline — formatDate can return raw ISO string on parse failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants