Skip to content

feat(slides): Session-scoped decks with Save, Enhance with AI, and versioning - #450

Merged
rschardosin merged 26 commits into
mainfrom
feat/docs-slides-agent-runtime
Aug 26, 2026
Merged

feat(slides): Session-scoped decks with Save, Enhance with AI, and versioning#450
rschardosin merged 26 commits into
mainfrom
feat/docs-slides-agent-runtime

Conversation

@rschardosin

@rschardosin rschardosin commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the Apps-like lifecycle for slide decks: decks created during a chat session are session-scoped (invisible in the Slides view), and must be explicitly Saved to become permanent. Saved decks can be enhanced via AI (creating a session-scoped draft copy), and override-saving archives the old version (up to 5 kept).

Changes

Schema

  • Added session_id, version, source_slug fields to Deck entity (personal + team scopes)
  • Added DeckVersion entity for version snapshots (up to 5 per deck)
  • Added ThumbnailReady to CreateDeck store method

Backend

  • Session scoping: Decks created during chat are tagged with session_id and filtered from the Slides list view
  • Session cleanup: Cascade-deletes session-scoped decks when a session is deleted
  • Save endpoint (POST /api/docs/slides/{slug}/save): Promotes session decks to permanent with optional override + versioning
  • Version endpoints: GET .../versions (list history) and POST .../versions/{v}/restore
  • Source copy (create_deck tool source param): Copies theme, assets, slides, thumbnails (ThumbnailReady + ThumbnailRef), and schema version from the original
  • Thumbnail baking: Triggered after save for immediate preview display in Slides view
  • Empty-deck resilience: Present endpoint returns a friendly placeholder instead of 500 when deck has no slides yet
  • Slug collision handling: Deletes stale session decks or appends hash suffix to avoid permanent deck collisions

Frontend

  • Save button in chat SlidesDeckView with inline title dialog (derives slug as saved-<slugified-title>)
  • Enhance with AI button on deck cards in SlidesView — navigates to chat with a prompt that creates a -draft session copy and instructs the model to modify it in-place
  • Version history panel in deck detail view (History button in header) with version list and Restore functionality
  • Version badge shown next to deck title when version > 1
  • API functions: saveDeck, listDeckVersions, restoreDeckVersion

Bug Fixes

  • Fixed slide copy using source slide IDs (UUID collision) — now generates fresh IDs
  • Fixed SaveDeckVersion passing invalid non-UUID string as ID — now auto-generates UUID
  • Fixed CreateDeck store method not persisting ThumbnailReady field
  • Fixed source-copy not preserving ThumbnailReady and slide ThumbnailRef
  • Fixed source-copy only copying SchemaVersion when TemplateModel was present
  • Fixed corrupt platform ent generated code (re-ran make ent-generate)

Architecture

Chat Session → create_deck (session_id tagged) → write_slide → review_deck (bakes thumbnails)
                                                                        ↓
Slides View ← Save button → POST /save → permanent deck (saved- prefix slug)
                                              ↓ (if slug exists)
                                        archive old version → DeckVersion entity
                                              ↓
                                        overwrite content, bump version

Enhance with AI → create_deck(source=slug) → session-scoped draft copy
                                                    ↓
                                              modify with write_slide
                                                    ↓
                                              Save → override original (archives old version)

Testing

  • go build ./...
  • go test ./pkg/store/... ./pkg/docs/slides/... ./pkg/api/...
  • cd web && npm run typecheck && npm test ✓ (621 tests)
  • make lint ✓ (0 issues)

Read quotas were blocking necessary file inspection, not duplicate
research. Keep graph/gap/search discovery caps and teach reuse in the
prompt instead. Also align terminal docs_update tests with the existing
Studio-only SSE mapping so the launcher package typechecks.
Replace the in-chat SlidesCard with a compact HarnessPlaceholder launcher
and a slides harness kind. DeckController accepts positional #slide-n
hashes so the embedded strip can navigate independently of author ids.
Adds an Apps-parity top-level "Slides" gallery for persisted decks, born
personal and promotable to team, with per-slide thumbnails.

Backend:
- slides.Service.CopyDeckTo copies a deck (manifest + all slides) between
  scopes, re-keying UUIDs via the validated WriteSlide path.
- ListDocsHandler merges personal + team decks with a per-deck scope
  annotation (in-memory only; no Ent schema change). Back-compat with ?scope=.
- New SlidesPublishToTeamHandler / SlidesForkToPersonalHandler + routes,
  registered before the {deckSlug} param routes.

Frontend:
- SlidesView: Personal/Team sections, deck cards with scaled sandboxed
  present-mode iframe thumbnails + titles, publish/fork/delete, deck detail
  reusing SlidesDeckView.
- slides.ts API: listSlidesDecks, publishDeckToTeam, forkDeckToPersonal,
  deleteSlidesDeck.
- Wired into TopBar nav, hash router, and App.tsx handlers.

Fix: buildPath had no 'slides' case, so the nav click fell through to
default '/chat' and silently redirected. Added the case + regression tests.

Tests: Go slides/api/store suites green; 545 frontend tests pass.
… on-demand slides skill

- ASD v2 as a strict superset of v1: raw colors, gradients, rotation, line
  dash/arrows, preset + custom geometry, image opacity, and multi-run rich
  text (ast-run). HTML/PPTX exporters and the Lit runtime accept both v1 and v2.
- Templates: theme tokens + title/section/content archetypes + assets, three
  built-ins (light-corporate, midnight, aurora) and scoped tmpl/ persistence;
  create_deck gains a template arg and a new slide_templates tool.
- PPTX import worker (jszip + fast-xml-parser) reconstructs an ASD v2 scene or
  Template from an uploaded .pptx, with structured loss diagnostics.
- New API routes: GET /api/docs/slides/templates and POST /api/docs/slides/import
  (request body bounded via http.MaxBytesReader).
- Frontend: templates UI in SlidesView + slides API client + runtime updates.
- Bundled on-demand slides skill (pkg/skills/builtin_content_slides.go): full
  authoring reference loaded via skill_lookup(slides); the always-on prompt
  keeps only a minimal pointer. Skill requires the user to choose a template
  (presented as a table) before authoring rather than silently picking one.
- Renamed the template-listing tool from list_templates to slide_templates to
  avoid a name collision with MCP servers exposing list_templates.
…s in PPTX export

The PPTX worker read prop keys the parser never emits (fontSize, bold,
valign, fontFamily, margin) and never read align at all, so exported text
was left-aligned, wrongly sized, non-bold, and mis-anchored vertically
versus the browser/HTML render.

Rewrite the text branch of worker.mjs to read the real parser keys that
AstText.ts uses (size, weight, align, anchor, color/color-token,
font/font-token, inset) with matching defaults (size 32, weight 400,
align left, anchor top, color-token ink, font-token body-font). Add:
- resolveColorToken/resolveFont helpers that resolve theme tokens against
  scene.theme with the same defaults as the HTML export
- ptFromPx to convert authored CSS px on the 1920x1080 canvas to points
  (72/160, since 160 canvas units = 1 inch)
- align/anchor maps to pptxgenjs align (left/center/right) and box-level
  valign (top/middle/bottom)

Update export_pptx_test.go to use real parser keys and assert the
DrawingML fidelity tokens (algn, b="1", vertical anchor, raw color).
…k) + export fixes

Slides PDF sandbox rendering:
- Add GetSlidesPDFBrowserManager + sandboxBrowserRequired to route slides PDF
  export through a dedicated per-user "slides-pdf-<user>" sandbox container when
  the sandbox is enabled, mirroring the Apps "app-mcp-<user>" pattern.
- ExportSlidesPDFHandler decides host-vs-container before scene load, uses a 90s
  cold-start timeout, and enforces a strict no-fallback contract: on a missing
  backend/callbacks it returns 500 with the real error and never renders on host.
- Reap warm slides-pdf-<user> sessions via the shared idle watchdog (10m).
- Document behavior + no-fallback contract in sandbox-backends.md (§14.2).
- Tests: slides_handlers_pdf_test.go (session-id derivation, host-only local
  manager, registered-callback wiring, no-fallback 500, sandbox-disabled path).

Earlier slides export fixes (bundled):
- Fix single-page export (print pagination CSS) and content dimension/cropping
  (align Go doc CSS and runtime-injected @media print to 20in x 11.25in).
- Add slog error logging on the PDF export path.
- Frontend runtime/style and deck-view updates with Vitest coverage.
…centering

Three fixes to make PPTX export match the HTML/PDF renders:

1. Spurious rectangle around content (shape border): the `shape` case forced a
   1pt 172033 outline on every shape, so fill-only panels (e.g. a full-slide
   background/frame) gained a faint rectangle. Now only draw an outline when the
   deck authored one (line color, width, dash, or arrow end); otherwise emit an
   explicit transparent line so nothing is drawn or inherited. Authored borders
   are preserved.

2. Alignment short forms: decks author align as `l|ctr|r` (the slides skill's
   vocabulary), passed through verbatim by the parser. The PPTX alignMap only
   knew the full CSS words, so align="ctr" fell through to left. Extend alignMap
   to accept l/ctr/c/r. Also fix the HTML/PDF runtime (AstText): it set
   `text-align` to the raw `ctr` (invalid CSS -> left); add alignToCSS() to map
   ctr/l/r -> center/left/right so all formats center identically.

3. Off-center content (root cause of the ~5% left shift): the scene canvas is
   1920x1080 at 160 units/inch == 12in x 6.75in, but the worker used pptxgenjs
   LAYOUT_WIDE (13.333in x 7.5in), placing 12in content on a wider slide with all
   slack on the right/bottom. Define a custom ASTONISH_CANVAS layout of exactly
   12in x 6.75in so element geometry (and centering) maps 1:1 to HTML/PDF.

Tests: TestPPTXShapeBorderMatchesAuthoring, TestPPTXTextHonorsShortFormAlignment,
TestPPTXLayoutMatchesCanvasAndCenters (Go), and AstText.test.ts alignToCSS cases.
…ories

- slides chat tool: list_templates returns compact summaries (name/label/
  description/scope/archetype kinds) instead of full template payloads;
  full archetype markup/theme/assets are seeded only on create_deck
- update slides skill doc to match the lightweight catalog workflow
- agent: tool_categories + plan-mode gating adjustments; uncap source reads
- launcher: surface builtin slides skill in the skills picker
- docs: refresh docs-slides and sandbox-backends architecture notes
…zer fidelity path

- Add dedicated Studio Templates area (list/delete/duplicate/recolor)
  with scope badges and archetype chips; wire router + App.tsx.
- Backend: Service.Template/TemplateSlug, themes.ArchetypesFor,
  slides_templates_handlers list DTO + Delete/Duplicate/Recolor endpoints.
- Fix .pptx import regression: add import path to largeEndpoints, raise
  maxImportPPTXBytes to 75MiB, return friendly 413 on oversize upload.
- Remove pptx-automizer template-fidelity export path: revert to pure
  PptxGenJS exporter, drop OriginPPTX/OriginAsset/HasOrigin, remove
  pptx-automizer npm dep (keep jszip + pptxgenjs). Import still yields a
  lossy ASD template. Update docs + slides skill prose.
- Tests: slides.test.ts, SlidesView.templates.test.tsx, Go handler/service
  tests updated; frontend 255 pass, go build/test + make lint green.
The Slides list was slow to load: every DeckCard fetched its full deck
markup on mount and eagerly mounted up to 5 live sandboxed iframes, each
booting the whole Lit slides runtime (custom elements + DeckController
listeners + ResizeObserver + font/image waits). With N decks that meant N
extra HTTP round-trips and up to N*5 runtime boots at once.

- Render a single first-page thumbnail per card (captioned by deck title).
- Lazy-mount that iframe via IntersectionObserver (rootMargin 200px); off-
  screen cards show a lightweight icon placeholder in the same aspect-video
  box (no layout shift). Falls back to eager render when IntersectionObserver
  is undefined (jsdom/tests).
- Drop the per-card fetchSlidesDeck call and the MAX_THUMBS strip; remove now-
  unused fetchSlidesDeck/SlidesSlide imports.

No backend/store/DeckManifest/present-runtime changes. Updated SlidesView
test for the single-thumbnail behavior (9/9 pass); typecheck/lint/build green.
Previously the skill told the agent to 'pick sensible defaults and proceed',
which led it to silently auto-select a template (e.g. midnight) instead of
letting the user choose.

Strengthen the rule in three places (Workflow step 1, Gathering Requirements,
Quick Checklist): unless the user named a template or explicitly delegated the
choice, the agent must STOP and present the list_templates options for the
user to pick before calling create_deck. An inferred tone/brand is not
permission to choose.

Skill/prose only; verified with go build ./pkg/skills/ and go vet.
…me, slim list reads

Imported corporate .pptx templates now render in full color and list fast.

Fidelity (colorful slides):
- Resolve master→layout background + chrome inheritance in import_worker.mjs so
  cover/divider layouts carry their real image/color backgrounds and accent shapes.
- Classify layouts into role variants (title/section/agenda/closing/content) by
  layout name, labeled with the real PowerPoint layout name.
- Drop the white slide-derived example-* archetypes (authored slides carry no bg).

Performance:
- Add DocsStore.ListDecksLite with Ent field projection (omit template_model +
  assets) for the Slides list path; full ListDecks retained for ListTemplates and
  Scene/exporters.

Also:
- Skill guidance, Templates UI chips, tests, and docs/architecture/docs-slides.md
  updated for labeled layout variants.
- Fix TestExportPlatformBackup panic: remove stray "// +build tools" constraint
  that had been added to generated ent/org/runtime.go, which excluded its init()
  and left team.DefaultCreatedAt nil.
Imported-template fidelity + AI image workflow for Astonish Slides.

Font rendering (import_worker.mjs):
- Scale imported run font sizes to the ASD 1920x1080 canvas using the same
  `scale` as geometry (applied once in styleOf). A 10pt footer now emits
  size~15 instead of a tiny size=10 inside a 1.5x-enlarged box.
- Inherit size/font for runs lacking explicit @sz/a:latin from the shape's
  own a:lstStyle, else master/layout p:txStyles (title/body/other), instead
  of falling to the hard-coded 24pt/serif default. Defensive: absent -> no-op.
- Append a web-safe fallback chain to emitted fonts
  (e.g. "72 Brand, Aptos, Arial, sans-serif") so uninstalled corporate fonts
  degrade to sans-serif rather than the browser default serif.
- Resolve +mj-*/+mn-* theme font tokens to concrete families (no token leak).

Deck image assets (tools.go, service.go):
- Add a lightweight per-deck asset catalog (ref/mime/bytes/kind hints, never
  data: URIs) surfaced on create_deck/get_deck via deckViewWithAssets.
- New tool list_deck_assets: enumerate a deck's images and their asset-refs.
- New tool add_deck_image: fetch a public https image via the SSRF-protected
  AssetIngestor and add it under sha256-<hex>, persisted with Service.AddDeckAsset
  (UpdateDeck, idempotent); returns the asset-ref for use in ast-image.

Classification / picture slots (prior work, included):
- Signature-first kindOf so Title-and-*/N-Columns/Q&A are flexible content,
  only genuine cover/divider/agenda/thank-you stay fixed chrome.
- Empty picture placeholders emit fillable image drop-slots (in fillSlots),
  not dead neutral panels; borrowed hero images keep sample geometry/flip.
- showMasterSp=0 warns only on genuine chrome loss.

Tests & docs: new/updated Go tests (font-scale, list_deck_assets,
add_deck_image, AddDeckAsset, classification, picture slots) and frontend
tests; docs/architecture/docs-slides.md updated. All Go + web tests and lint pass.
…t-face

Imported corporate templates set the deck theme to concrete brand
families (e.g. "72 Brand"), but the runtime only carried the family
NAME as a string - no font file was ever extracted or loaded, so the
browser fell back to its default serif (Times).

Font-embedding pipeline:
- import_worker.mjs collectEmbeddedFonts() reads p:embeddedFontLst,
  resolves each variant's .fntdata via rels, strips the EOT wrapper
  (MTX-decompressing via mtx-decompressor) to recover TTF/OTF, and
  stores each face as a font:<family>:<variant> data: asset plus an
  "embedded-fonts" theme manifest.
- fonts.go: EmbeddedFontRef + parseEmbeddedFonts; export_html.go
  writeFontFaces emits @font-face rules (CSP already allows font-src
  data:); writeThemeCSS skips the embedded-fonts key.
- tools.go assetCatalog skips font: keys so fonts never leak into the
  image catalog or API responses.

CSS-quoting fix (the actual "still Times" symptom):
A family that starts with a digit ("72 Brand") is an invalid UNQUOTED
CSS identifier, so the browser dropped the whole font-family
declaration and fell back to serif even with the @font-face present.
Both the importer (cssFontFamilyName/withFontFallback) and the runtime
choke point (cssFontFamily in AstText.ts) now double-quote families
that start with a digit or contain unsafe chars. The runtime fix is
idempotent and also corrects decks stored before this change.

Tests + docs for EOT stripping, extraction, @font-face emission, the
catalog font guard, and cssFontFamily; docs-slides.md documents the
pipeline and the digit-leading quoting rule.
… with slides variant picker

Add a generic, reusable chat-question primitive the agent can render inline:
- Backend [chat_question] event contract (persist + reload reconstruction),
  ask_user tool, and get_template_variant_previews; chat_runner emits the card
  and ends the turn so the user's click returns as a normal user message.
- Frontend YesNoCard / SingleSelectCard (optional thumbnail slot) + live
  SlidesArchetypeThumb rendering an ast-deck miniature; TUI text-fallback parity.
- Slides skill instructs ask_user (one question at a time) over plain-text lists.

Fixes found in review:
- Variant picker only showed one option: getTemplateVariantPreviews now matches
  the role family (title/title-2/...) via stripVariantSuffix, and ask_user
  generates unique ids/labels for empty/duplicate variant labels.
- App crash on the question card: no error boundary existed, so a render throw
  blanked the whole SPA. Add ChatErrorBoundary around the card and guard
  option mapping; harden SlidesArchetypeThumb mount with try/catch.
- OpenAI 400 'string too long' (~366MB): thumbnails embedded the full base64
  asset map per variant into the tool result and persisted history. Drop the
  asset map everywhere; carry the template NAME and resolve asset-refs on the
  client at render time (cached listSlidesTemplates fetch).
…atic

Promote the visual questionnaire to an explicit, mandatory workflow step
(step 3, before authoring): the static picks (title/agenda/divider) PLUS up
to 5 adaptive content questions chosen from the actual material — chart vs.
table, timeline vs. list, comparison layout, emphasis/closing slides, etc.

Reframe from a soft ceiling to a floor+ceiling: when the content shows a
strong signal (numbers, comparison, dates/phases, process, notable length) the
model MUST ask at least one adaptive question rather than defaulting to
bullets, while still capping at 5 and skipping weak signals to avoid fatigue.
Gate write_slide on the questionnaire and honor answers when authoring.
…on import

Imported title placeholders rendered black even when the source PPTX
authored them white (e.g. a title on a dark blue cover). The importer
only captured text color from run-level a:rPr/a:solidFill, but empty
layout title placeholders carry no runs — their color is inherited from
the master <p:txStyles> titleStyle defRPr solidFill (or the shape's own
<a:lstStyle> defRPr override). That inheritance was dropped, so the
placeholder fell back to the renderer default (themeTokens.ink / black).

- parseTxStyles: also capture defRPr solidFill color (not just size/font)
- extractRuns: capture lstStyle defRPr color into localDefaults, inherit
  it for runs with no explicit color, and return the resolved defaults
- classify: backfill placeholder style.color/size/font from the shape's
  own lstStyle defaults (tightest) then master txStyles when styleOf
  found no run — so empty title placeholders render in their authored color

Other placeholders (mostly black on white slides) are unaffected because
their inherited color already resolves to the same value.
… PPTX export

Two template-export regressions:

1. Image bytes: the PPTX worker required a pre-resolved props.data and hard-
   failed ('image c-3 has no validated data') when a deck authored images as
   <ast-image asset-ref>. Resolve the ref against scene.assets (mirroring the
   HTML exporter), accept only data:image/*, and degrade an unresolvable ref to
   a skipped image with a warning instead of failing the whole export.

2. Font stacks: pptxgenjs writes fontFace verbatim into <a:latin typeface>
   without escaping. Imported-template theme fonts are full CSS font-family
   stacks with embedded quotes (e.g. '"72 Brand", Aptos, Arial, sans-serif'),
   producing malformed OOXML (typeface=""72 Brand", ...") so PowerPoint
   recovered the file and blanked the affected slides. Reduce any CSS stack to a
   single, quote-free family name at every fontFace path (theme, resolveFont,
   per-run).

Adds TestPPTXFontStackProducesWellFormedXML asserting every OOXML part parses.
Extend ask_user with slidesTemplatePicker: for the first 'which template should
I use?' question, enumerate every available template (built-in + imported) and
generate one select option per template, each carrying a live cover-slide
thumbnail (its first title archetype). Thumbnails resolve asset-refs on the
client from the template's own asset map; no data: bytes in the tool result.
Updates the slides skill and docs to drive the visual template choice.
Bake each slide to a small static PNG (320×180, ~5-15 KB) when a deck is
finished (review_deck), serve it at GET /api/docs/slides/{slug}/thumbnails/{idx},
and render it in the Slides view as an <img> with an empty placeholder fallback
— never a live ast-deck iframe render.

Schema:
- Add thumbnail_ref column to Slide entity (personal + team scopes)
- Add ThumbnailRef to store.SlideContent, wire through UpsertSlide/fill*Slide
- Ent auto-migrates at boot; no SQL migration files needed

Backend:
- pkg/docs/slides/deck_thumbnails.go: GenerateDeckThumbnails baker (idempotent,
  best-effort, nil-browser safe, injectable renderer for tests)
- pkg/pdfgen/chrome.go: add Scale field to ScreenshotOptions; Scale < 1 reduces
  the viewport dimensions and uses viewport-only capture (not fullPage) so the
  runtime's DeckController scales the fixed canvas into the smaller viewport
- Versioned asset key (slidethumb/v2/<pos>) so existing full-res thumbnails
  re-bake to the small format on next review_deck
- Same 1/6 downscale applied to template archetype thumbnails
- GET /api/docs/slides/{deckSlug}/thumbnails/{idx} endpoint with immutable cache
- Trigger: maybeEmitDocsUpdate fires bakeDeckThumbnails in a goroutine on
  ActionDeckReviewed (new constant); uses canonical sandbox/local browser pattern
- Deck finishing never fails when browser is unavailable

Frontend:
- SlidesView.tsx SlideThumbnail: <img> from deckSlideThumbnailUrl with onError
  placeholder (no live render fallback); removed pointer-events-none
- web/src/api/slides.ts: thumbnailRef field + deckSlideThumbnailUrl helper
- Tests: SlidesView renders img not iframe, onError shows placeholder

Docs:
- docs/architecture/docs-slides.md: 'Static deck slide thumbnails' section
Add a thumbnail_ready boolean to the Deck entity (personal + team scopes).
GenerateDeckThumbnails sets it to true after baking. The list DTO now carries
thumbnailReady so the frontend's SlideThumbnail component can skip the <img>
entirely when no thumbnail exists — showing the placeholder icon immediately
without issuing 5+ wasted 404 HTTP requests that delayed the Slides view.
…rsioning

- Decks created during a chat session are tagged with session_id and
  hidden from the Slides view until explicitly saved.
- Session deletion cascades to remove all session-scoped decks.
- Save button in chat promotes session decks to permanent (saved- prefix slug).
- Enhance with AI creates a session-scoped copy (source parameter on
  create_deck tool) preserving theme, assets, slides, thumbnails, and
  schema version from the original.
- Override-save archives the old version (DeckVersion entity, up to 5 kept).
- Version history UI in Slides detail view with restore capability.
- POST /api/docs/slides/{slug}/save endpoint with new-deck and override flows.
- GET /api/docs/slides/{slug}/versions and POST .../restore endpoints.
- Thumbnail baking triggered after save for immediate preview display.
- Empty-deck present endpoint returns friendly placeholder instead of 500.
- Slug collision handling in source-copy path (deletes stale session decks,
  appends hash suffix to avoid permanent deck collisions).

Schema changes:
- Added session_id, version, source_slug fields to Deck entity (personal+team).
- Added DeckVersion entity for version snapshots.
- Added ThumbnailReady to CreateDeck store method.
@rschardosin rschardosin changed the title Add docs slides and harden agent execution feat(slides): Session-scoped decks with Save, Enhance with AI, and versioning Aug 26, 2026
@rschardosin
rschardosin merged commit b3b6636 into main Aug 26, 2026
4 checks passed
@rschardosin
rschardosin deleted the feat/docs-slides-agent-runtime branch August 26, 2026 04:11
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.

1 participant