Skip to content

refactor(renderer): type sidebar + top-level SFCs β€” drop @ts-nocheck (TS migration follow-up 4/6)#4252

Merged
Jocs merged 4 commits into
developfrom
ts/sfc-sidebar
May 22, 2026
Merged

refactor(renderer): type sidebar + top-level SFCs β€” drop @ts-nocheck (TS migration follow-up 4/6)#4252
Jocs merged 4 commits into
developfrom
ts/sfc-sidebar

Conversation

@Jocs
Copy link
Copy Markdown
Member

@Jocs Jocs commented May 22, 2026

Summary

Part 4/6 of the TypeScript migration deferred-work cleanup. Drops // @ts-nocheck from the sidebar SFCs and the top-level page shells.

Stacked on #4249 (ts/store-setup-rewrite). Will be retargeted to develop automatically once #4249 merges.

Changes (13 files)

  • 9 sideBar SFCs: tree.vue, treeFile.vue, treeFolder.vue, treeOpenedTab.vue, icon.vue, index.vue, search.vue, searchResultItem.vue, toc.vue.
  • 3 top-level pages: Main.vue, pages/app.vue, pages/preference.vue.
  • New shared types in src/renderer/src/components/sideBar/types.ts:
    • TreeFileNode, TreeFolderNode, TreeNode β€” narrow versions of the runtime shapes built by treeCtrl.ts.
    • SearchRange, SearchMatch, SearchResult β€” ripgrep result shape consumed by search.vue / searchResultItem.vue.
    • TabDescriptor β€” alias of IFileState at the sideBar boundary.
  • src/types/global.d.ts β€” broadened window.marktext to include initialState and paths.

Notable adjustments

  • tree.vue β€” added a createCacheDirname computed because the underlying createCache ref union ({ dirname, type } | {}) doesn't expose dirname directly.
  • search.vue β€” refactored to hold the CancellableSearch handle separately from the chained .then().catch() (the chained promise becomes Promise<void> and loses .cancel).
  • app.vue β€” tightened the addStyles boundary with explicit coalescing against DEFAULT_STYLE because URL-parsed initialState carries nullables.
  • app.vue / preference.vue β€” spelled out /index.vue on directory imports: vue-tsc's bundler resolver doesn't follow Vite's directory-with-index.vue convention without an explicit path.

Test plan

  • pnpm typecheck β€” clean
  • pnpm test:unit β€” 550/550 pass
  • pnpm exec playwright test test/e2e/launch.spec.ts test/e2e/tabs.spec.ts β€” 3/3 pass

πŸ€– Generated with Claude Code

Jocs and others added 2 commits May 22, 2026 16:51
- Drop `// @ts-nocheck` from `editor.ts` (1,737 lines) and
  `preferences.ts` (197 lines). Both remain Options Stores; Pinia's
  Options-Store typing is fully inferrable from a typed `state: () =>
  State` factory, so converting to Setup Store would churn ~80 call sites
  for no expressive gain.
- Consolidate `IFileState` (in `@shared/types/files.ts`) and
  `IDocumentState` (in `store/help.ts`) into a single canonical shape;
  `IDocumentState` becomes an alias re-exported from `help.ts`.
- Replace the `currentFile: {}` empty-object sentinel with
  `IFileState | null`; the two `hasKeys(this.currentFile)` sites become
  explicit null checks, and `?? {}` fallbacks become `?? null`.
- Restore the missing `RENAME_IF_NEEDED` action that `project.ts` was
  calling through an `(editorStore as any)` cast (legacy action dropped
  in the Vuex→Pinia port). Cast + eslint-disable removed.
- Tighten the IPC contract entries that mismatched the runtime
  payloads: `mt::rename`, `mt::response-file-move-to`,
  `mt::window-tab-closed`, `mt::close-window-confirm`,
  `mt::export-success`, `mt::response-print`, `mt::update-format-menu`;
  add `mt::response-file-save-as`. `SaveOptions.encoding` now accepts
  the runtime `FileEncoding` object as well as the legacy string form.
- Trim the "Deferred work" item for editor.ts and update the Pinia
  stores section in `docs/dev/TYPESCRIPT.md` to reflect that the
  remaining Options Stores are typed.

Verified: `pnpm typecheck` clean; `pnpm test:unit` 550/550 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop @ts-nocheck from the sideBar component group (9 SFCs) and the
top-level Main.vue / pages (app.vue, preference.vue).

- Add src/renderer/src/components/sideBar/types.ts with shared TreeNode,
  TreeFileNode, TreeFolderNode, SearchResult/SearchMatch and TabDescriptor
  shapes β€” drawn from treeCtrl + ripgrep bridge runtime payloads.
- Type defineProps via the generic form for every sideBar SFC.
- Type element refs (HTMLInputElement / HTMLDivElement) where mouse/key
  handlers need them.
- Narrow window.marktext.initialState in global.d.ts so app/preference
  pages can read theme/codeFontFamily without casts. addStyles in
  app.vue now coalesces nullable URL params against DEFAULT_STYLE.
- Refactor search.vue to hold a typed CancellableSearch handle instead
  of trying to read `.cancel` off a chained Promise<void>.
- Fix unresolved bare-directory imports (`@/components/foo`) in app.vue
  and preference.vue by spelling out `/index.vue` (vue-tsc, unlike Vite,
  doesn't auto-resolve directories with an index.vue).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

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 is part of the renderer TypeScript migration follow-ups: it removes // @ts-nocheck from the sidebar and top-level page SFCs by adding explicit prop/types, and introduces shared sidebar type definitions plus a broader window.marktext global type.

Changes:

  • Removed // @ts-nocheck across sidebar components and top-level page shells; added explicit prop typings and safer null-handling in templates/handlers.
  • Introduced shared sidebar domain types (Tree*, search result shapes, TabDescriptor) to reduce ad-hoc any/casts across SFCs.
  • Expanded window.marktext typing to include initialState and paths to support typed bootstrap data access.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/types/global.d.ts Extends window.marktext typing for bootstrap initialState and paths.
src/renderer/src/pages/preference.vue Drops @ts-nocheck, uses explicit .vue imports, and hardens initial theme style application.
src/renderer/src/pages/app.vue Drops @ts-nocheck, strengthens types around timers/props, and coalesces nullable bootstrap style values.
src/renderer/src/Main.vue Removes @ts-nocheck (empty script setup).
src/renderer/src/components/sideBar/types.ts Adds shared TS types for sidebar tree/search/tab surfaces.
src/renderer/src/components/sideBar/treeOpenedTab.vue Adds typed props (TabDescriptor) and null-safe current file comparisons.
src/renderer/src/components/sideBar/treeFolder.vue Adds typed props/refs and explicit return types for handlers.
src/renderer/src/components/sideBar/treeFile.vue Adds typed props/refs and null-safe current file comparisons.
src/renderer/src/components/sideBar/tree.vue Adds typed props and a typed accessor for createCache.dirname to remove template casts.
src/renderer/src/components/sideBar/toc.vue Drops @ts-nocheck and types the TOC click handler payload.
src/renderer/src/components/sideBar/searchResultItem.vue Drops @ts-nocheck and types computed values/handlers for search result rendering.
src/renderer/src/components/sideBar/search.vue Drops @ts-nocheck, types search state, and refactors cancellable ripgrep usage.
src/renderer/src/components/sideBar/index.vue Drops @ts-nocheck, types refs/width computations, and guards missing drag bar element.
src/renderer/src/components/sideBar/icon.vue Drops @ts-nocheck and makes computed icon classes null-safe.
Comments suppressed due to low confidence (1)

src/renderer/src/components/sideBar/search.vue:264

  • The parameter type executeSearch: boolean | unknown = true collapses to unknown (since unknown absorbs unions), which defeats the intent of typing and makes if (executeSearch) effectively accept any value. Prefer a plain boolean parameter (and, if callers may pass non-boolean, coerce/validate inside) so the function’s contract is clear and type-safe.
const handleFindInFolder = (executeSearch: boolean | unknown = true): void => {
  nextTick(() => {
    if (searchEl.value) {
      searchEl.value.focus()
      // `searchMatches.value` may carry a `selectedText` populated elsewhere
      // (legacy contract from CodeMirror / find-in-page). Narrow defensively.
      const selectedText = (searchMatches.value as { selectedText?: string } | undefined)
        ?.selectedText
      if (selectedText) {
        keyword.value = selectedText
        if (executeSearch) {
          search()
        }
      }

πŸ’‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const handleClick = ({ slug }) => {
bus.emit('scroll-to-header', slug)
const handleClick = (data: { slug?: unknown }): void => {
bus.emit('scroll-to-header', data.slug)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in acf88e9b. Now narrows to a non-empty string and bails out early:

if (typeof data.slug !== 'string' || data.slug.length === 0) return
bus.emit('scroll-to-header', data.slug)

tabs: Array
})
const props = defineProps<{
projectTree: TreeNode
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in acf88e9b β€” typed as TreeNode | null with a comment noting that the project store seeds it as null until a folder is opened, and the template's v-if="projectTree" guard now matches the prop type.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 22, 2026

Build artifacts for PR #4252:

Run: https://github.com/marktext/marktext/actions/runs/26285880720

Artifact Size Link
marktext-macos-x64 534.9 MB Download
marktext-linux 594.5 MB Download
marktext-windows 272.8 MB Download
marktext-macos-arm64 534.6 MB Download

- toc.vue: narrow `data.slug` to a non-empty string before emitting
  `scroll-to-header`. editor.vue builds a CSS selector with `#${slug}`, so
  forwarding `unknown`/`undefined` could produce `#undefined` selectors and
  silently no-op the scroll.
- tree.vue: type `projectTree` as `TreeNode | null` to match the runtime
  shape (the project store seeds it as `null` until a folder is opened) and
  the template's existing `v-if="projectTree"` guard. Non-nullable type made
  the script section unsound and would hide future null-handling bugs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Jocs Jocs changed the base branch from ts/store-setup-rewrite to develop May 22, 2026 11:32
# Conflicts:
#	docs/dev/TYPESCRIPT.md
#	src/renderer/src/store/editor.ts
#	src/shared/types/files.ts
#	src/shared/types/ipc.ts
@Jocs Jocs merged commit 963342c into develop May 22, 2026
8 checks passed
Jocs added a commit that referenced this pull request May 22, 2026
…S migration follow-up 7/7) (#4255)

* refactor(test): port specs to strict TS (drop @ts-nocheck, ESM imports)

Convert all 17 e2e specs and the e2e helpers module from CommonJS
`require()` to ESM `import`, drop `// @ts-nocheck` from every test file,
and add real types for the Playwright/Electron handles (ElectronApplication,
Page) plus the helper return shapes. Vitest specs gain explicit
`import { describe, it, expect, ... } from 'vitest'` so they no longer
rely on the implicit globals when type-checked.

Also adds the `main_renderer/*` path alias (already in vitest.config.ts)
to tsconfig.base.json so the native-theme unit spec resolves against
src/main from TS.

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

* refactor(renderer): type prefComponents schemas and leaf SFC controls

Removes // @ts-nocheck from the preference-component schema modules and
the leaf input controls (bool, compound, fontTextBox, range, select,
textBox, titlebar), plus a few related TS files (window-controls,
exportSettings/exportOptions, sideBar/help, image uploader services,
KeybindingConfigurator).

Adds prefComponents/common/types.ts with shared PrefControlBaseProps /
PrefControlProps<T> / PrefSelectOption<T> helpers; per-control props
extend the base with their specific value-prop name and onChange
signature.

Static option arrays in each */config.ts now return
PrefSelectOption<T>[]; the static `themes` array in theme/config.ts
gains a ThemeDescriptor type; KeybindingConfigurator exposes a typed
UiKeybinding shape and Map<string, string> fields. sideBar/config.ts
adds a local Window.__VUE_I18N__ shape so the debug / language-polling
helpers no longer need a blanket nocheck.

* refactor(stores): type editor + preferences Pinia stores

- Drop `// @ts-nocheck` from `editor.ts` (1,737 lines) and
  `preferences.ts` (197 lines). Both remain Options Stores; Pinia's
  Options-Store typing is fully inferrable from a typed `state: () =>
  State` factory, so converting to Setup Store would churn ~80 call sites
  for no expressive gain.
- Consolidate `IFileState` (in `@shared/types/files.ts`) and
  `IDocumentState` (in `store/help.ts`) into a single canonical shape;
  `IDocumentState` becomes an alias re-exported from `help.ts`.
- Replace the `currentFile: {}` empty-object sentinel with
  `IFileState | null`; the two `hasKeys(this.currentFile)` sites become
  explicit null checks, and `?? {}` fallbacks become `?? null`.
- Restore the missing `RENAME_IF_NEEDED` action that `project.ts` was
  calling through an `(editorStore as any)` cast (legacy action dropped
  in the Vuex→Pinia port). Cast + eslint-disable removed.
- Tighten the IPC contract entries that mismatched the runtime
  payloads: `mt::rename`, `mt::response-file-move-to`,
  `mt::window-tab-closed`, `mt::close-window-confirm`,
  `mt::export-success`, `mt::response-print`, `mt::update-format-menu`;
  add `mt::response-file-save-as`. `SaveOptions.encoding` now accepts
  the runtime `FileEncoding` object as well as the legacy string form.
- Trim the "Deferred work" item for editor.ts and update the Pinia
  stores section in `docs/dev/TYPESCRIPT.md` to reflect that the
  remaining Options Stores are typed.

Verified: `pnpm typecheck` clean; `pnpm test:unit` 550/550 passing.

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

* refactor(renderer): type sidebar + top-level SFCs (drop @ts-nocheck)

Drop @ts-nocheck from the sideBar component group (9 SFCs) and the
top-level Main.vue / pages (app.vue, preference.vue).

- Add src/renderer/src/components/sideBar/types.ts with shared TreeNode,
  TreeFileNode, TreeFolderNode, SearchResult/SearchMatch and TabDescriptor
  shapes β€” drawn from treeCtrl + ripgrep bridge runtime payloads.
- Type defineProps via the generic form for every sideBar SFC.
- Type element refs (HTMLInputElement / HTMLDivElement) where mouse/key
  handlers need them.
- Narrow window.marktext.initialState in global.d.ts so app/preference
  pages can read theme/codeFontFamily without casts. addStyles in
  app.vue now coalesces nullable URL params against DEFAULT_STYLE.
- Refactor search.vue to hold a typed CancellableSearch handle instead
  of trying to read `.cancel` off a chained Promise<void>.
- Fix unresolved bare-directory imports (`@/components/foo`) in app.vue
  and preference.vue by spelling out `/index.vue` (vue-tsc, unlike Vite,
  doesn't auto-resolve directories with an index.vue).

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

* refactor(renderer): type preference page SFCs (drop @ts-nocheck)

Removes the `// @ts-nocheck` suppression from the 12 preference window
page-level SFCs (the leaf controls under `prefComponents/common/` were
already typed in PR-B) and resolves the type errors that surface as a
result.

- General/Editor/Markdown/Theme/Image/Spellchecker/Sidebar pages now
  type the `onSelectChange(type, value)` mutator against
  `PreferencesState`, narrow the typed `storeToRefs` output, and use the
  shared `PrefSelectOption<T>` helper for inline option arrays.
- The image uploader page types its picgo detection state, awaits the
  Promise-returning `commandExists.exists` (which the original code
  invoked synchronously under `@ts-nocheck`), and narrows
  `UploaderServiceId` lookups.
- The keybindings page types the `KeybindingConfigurator` ref and the
  raw IPC return shapes (`mt::keybinding-get-keyboard-info` and
  `mt::keybinding-get-pref-keybindings` are still `ret: unknown` in the
  IPC contract; the cast is local).
- The key-input dialog now uses `defineProps<T>()` generics, types its
  `useTemplateRef` mount as `HTMLInputElement`, and types the
  KeyboardEvent handlers.

No production behavior changes. Verified via `pnpm typecheck` and
`pnpm test:unit` (550 tests pass).

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

* refactor(renderer): type editor + components SFCs (drop @ts-nocheck)

Remove `// @ts-nocheck` from all editorWithTabs/* and components/* SFCs
covered by PR-D.2: editor.vue, index.vue, tabs.vue, notifications.vue,
sourceCode.vue, about, commandPalette, exportSettings, import, loading,
recent, rename, search, titleBar.

- Convert defineProps to TS generic form; type bus listeners, DOM refs,
  and Element Plus instance refs (loose where Element Plus does not
  ship adequate types).
- Use PR-A's typed editor store: `IFileState`, `currentFile?.id` guards.
- Keep Muya/CodeMirror surfaces as local `any` aliases pinned near the
  top of each file; replace when upstream TS Muya lands.
- Fix lurking bug: rename dialog called `editorStore.rename(...)` which
  has always been `RENAME(...)` in the store.
- Fix lurking bug: sourceCode handleImageAction fallback called
  `setCursorAtFirstLine()` with no argument; passes `editor.value` now
  to mirror the other call site.
- Add a minimal `underscore` shim (debounce only) to src/types/shims.d.ts
  β€” no @types/underscore on npm and we only consume `debounce`.

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

* fix(merge): align cursor prop type with IFileState.cursor: unknown

After merging PR-A (stores) with PR-D.2 (editor SFCs), the cursor prop
in editorWithTabs/index.vue and editor.vue still declared `object`/`object | null`
even though the typed editor store now returns `unknown` for cursor.
Widen the prop type to `unknown` at both boundaries β€” the prop is forwarded
verbatim into muya without runtime introspection, so unknown is the honest
shape.

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

* refactor(any): replace `any` with `unknown`/proper types in 4 hot spots

- src/main/ipc/fs.ts: cast through `string | NodeJS.ArrayBufferView` instead of `any` when forwarding to fs-extra writeFile/outputFile.
- src/renderer/src/contextMenu/tabs/index.ts: MenuItemShape uses `(...args: unknown[]) => void` and `[key: string]: unknown`.
- src/renderer/src/store/commandCenter.ts: reuse the existing `CommandDescriptor` from src/renderer/src/commands instead of a duplicate `Command` shape; tighten `normalizeAccelerator` return to `string[]` (both branches always returned string[]).
- src/shared/types/typedEmitter.ts: replace the `any[]` fallback in the conditional listener-args type with `unknown[]`.

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

* chore(eslint): tighten @typescript-eslint/no-explicit-any to error

- src/types/muya.d.ts: file-level disable with explanatory comment β€” this
  ambient bridge to legacy JS muya is intentionally `any` until upstream TS
  muya (https://github.com/marktext/muya) replaces it.
- src/main/filesystem/watcher.ts: chokidar's `ignored` callback options bag
  defies the bundled `WatchOptions` type; targeted line disable with a
  `--` justification, plus an explicit comment.
- eslint.config.js: flip the rule from `warn` to `error` for the .ts scope.
- docs/dev/TYPESCRIPT.md: rewrite the "Deferred work" section β€” every item
  landed across PRs #4249–#4255.

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

* fix(stores): address Copilot review feedback on PR #4249

- BootstrapEditorConfig.markdownList: MarkdownDocument[] β†’ string[]. Main
  process actually sends raw markdown strings from `_markdownToOpen` (see
  src/main/windows/editor.ts:162). Drop the `md as unknown as string` cast
  in the renderer bootstrap path; pass `md` through directly.
- mt::close-window-confirm: tighten the IPC contract from
  `[unsavedFiles?: unknown[]]` to `[unsavedFiles: UnsavedFile[]]` (required,
  not optional β€” main iterates the array and crashes on undefined).
- Lift UnsavedFile to src/shared/types/files.ts so renderer and main share
  one source of truth. Internal writeMarkdownFile call sites cast through
  `Parameters<typeof writeMarkdownFile>[2]` instead of the previous
  `type SaveOptions = any` shim.
- RENAME_IF_NEEDED now updates `window.DIRNAME` when the renamed tab is the
  active currentFile, so dirname-based link resolution doesn't keep using
  the old folder until the user switches tabs.

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

* fix(ipc): tighten mt::keybinding-save-user-keybindings ret to boolean

The main-process handler (src/main/app/index.ts:828) returns the boolean from
`setUserKeybindings()` (src/main/keyboard/shortcutHandler.ts:123), but the IPC
contract typed `ret` as `void`. KeybindingConfigurator.save() was
double-casting through `as unknown as boolean` to compensate. Drop the cast
and let the contract speak for itself.

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

* fix(test): use Date#getDate() (day-of-month) in helpers.ts temp filename

`getDateAsFilename()` was using `Date#getDay()` (0–6, day of week) instead of
`Date#getDate()` (1–31, day of month). The generated temp-directory prefix
didn't actually reflect the calendar date and could collide across different
days that fall on the same weekday β€” exacerbated by short random suffixes
and parallel Playwright workers.

Pre-existing bug from before the TS port (test/e2e/helpers.js); flagged by
Copilot review on PR #4251.

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

* fix(sideBar): address Copilot review feedback on PR #4252

- toc.vue: narrow `data.slug` to a non-empty string before emitting
  `scroll-to-header`. editor.vue builds a CSS selector with `#${slug}`, so
  forwarding `unknown`/`undefined` could produce `#undefined` selectors and
  silently no-op the scroll.
- tree.vue: type `projectTree` as `TreeNode | null` to match the runtime
  shape (the project store seeds it as `null` until a folder is opened) and
  the template's existing `v-if="projectTree"` guard. Non-nullable type made
  the script section unsound and would hide future null-handling bugs.

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

* fix(editor): address Copilot review feedback on PR #4253

- editor.vue `imageAction` folder branch: pass `currentPathname` instead of
  `null as unknown as string`. `moveImageToFolder` calls
  `path.dirname(pathname)` whenever the image is a string (e.g. paste / drag /
  image-selector), which would crash on `dirname(null)`. Using
  `currentPathname` resolves relative paths against the active file's dir,
  matching the sibling call sites.
- sourceCode.vue `onMounted`: reset `scrollTop` to `0` rather than
  `undefined as unknown as number`. `IFileState.scrollTop: number` is
  non-optional; comparisons elsewhere (`> currentFile.scrollTop`) would
  otherwise see `undefined` and behave oddly.

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

* fix(uploader): persist agreedToLegalNotices on the cached services singleton

`getServices()` builds a new object each call, so mutating
`getServices()[id].agreedToLegalNotices = true` only touched a throwaway
instance β€” the checkbox bound to the file-local `uploadServices` cache (line
384) never saw the update. Mutate `uploadServices` directly so the agreed
state survives.

Flagged by Copilot review on PR #4254.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Jocs Jocs deleted the ts/sfc-sidebar branch May 25, 2026 03:27
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