Skip to content

chore(version-15): bring latest develop changes to version-15#143

Merged
harshtandiya merged 97 commits into
version-15from
chore/backport-develop-to-version-15
May 24, 2026
Merged

chore(version-15): bring latest develop changes to version-15#143
harshtandiya merged 97 commits into
version-15from
chore/backport-develop-to-version-15

Conversation

@harshtandiya
Copy link
Copy Markdown
Collaborator

What

Brings the latest develop changes to the version-15 maintenance branch.

version-15 was last synced via squash backport (#86), so it had drifted ~95 commits behind develop (CSV/Excel export, route-level permissions, multi-column layout, heading field types, multiselect, e2e tests, dep bumps, etc.).

How

  • Branched from develop, then git merge -s ours upstream/version-15 to record version-15 as a parent for clean ancestry while taking develop's tree as the content baseline.
  • Re-applied the version-15 deployment layer on top:
    • pyproject.toml: Frappe pinned >=15.0.0,<16.0.0; requires-python >=3.10; Faker ~=38.2.0; ruff target-version = py310.
    • forms_pro/init.py: version 15.0.0.
    • CI / linter / typecheck / ui-tests workflows: retargeted to version-15 (Python 3.10, Node 20, MariaDB 10.6.24, bench init --version version-15); dropped merge_group triggers.
    • Tests: switched all test modules to frappe.tests.utils.FrappeTestCase for Frappe v15 compatibility (develop uses frappe.tests.IntegrationTestCase, which is v16-only).

Notes

  • Backend tests must run on a Frappe v15 bench (the version-15 CI builds one); they can't be validated on a local v16 bench.
  • ui-tests.yml was aligned to the version-15 runtime (py3.10/node20, bench init --version version-15) for consistency with the other workflows.

🤖 Generated with Claude Code

harshtandiya and others added 30 commits February 15, 2026 00:23
- Introduced a new section in pyproject.toml for frappe dependencies, specifying version constraints for better compatibility in benchmarking tasks.
* chore: bump frappe-ui to `0.1.262`

* feat: add 'Table' option to fieldtype selection

- Updated form_field.json and form_field.py to include 'Table' in the list of selectable field types for enhanced functionality.
- Adjusted the modified timestamp in form_field.json to reflect recent changes.

* feat: implement Table field component and enhance field options handling

- Added a new Table component for rendering tabular data input.
- Updated RenderField and FieldRenderer components to support dynamic options for Select and Link fields.
- Introduced useFieldOptions composable for improved option management.
- Enhanced form_fields utility to include Table as a selectable field type.
- Updated auto-imports and TypeScript definitions to accommodate new functionality.

* feat: enhance Table component fieldtype mapping

- Integrated utility function to map fieldtype for Table component, ensuring proper handling of field types.
- Defaulted unmapped fieldtypes to "Data" for improved consistency in table rendering.

* fix: update TableField component to use the correct Table component

- Replaced ListView with Table in the TableField definition to ensure proper rendering of tabular data.
- Adjusted imports in form_fields utility to reflect the change in component usage.

* refactor: remove onMounted hook from useFieldOptions

- Eliminated the onMounted lifecycle hook from the useFieldOptions composable to streamline the loading process of options.
- Adjusted imports to reflect the removal of the unused onMounted function.
- Changed the frappe dependency version constraint from ">=17.0.0-dev,<18.0.0" to ">=16.0.0-dev,<18.0.0" for better compatibility.
- Changed `:model-value` to `:content` for TextEditor components across multiple files to ensure consistency in prop usage.
- Added an empty declaration block in auto-imports.d.ts for improved TypeScript support.
* chore: bump lucide-icons

* feat: restructure home dashboard and sidebar

- Introduced a new home dashboard layout with a dedicated Dashboard.vue component.
- Migrated existing dashboard functionality from the previous Dashboard.vue to the new home structure.
- Added sidebar items management through a new sidebarItems.ts file for better organization.
- Updated TeamSwitcher component to conditionally render based on the current team.
- Removed unused code and components to streamline the application.

* refactor: enhance layout structure in BaseLayout and Dashboard components

- Wrapped the slot in BaseLayout with a div for improved layout consistency.
- Removed unnecessary padding class from the Dashboard component for a cleaner design.
- Ensured consistent formatting in watch options for better readability.

* fix: manageform page layout

* feat: team invitations via User Invitations

* feat: implement team member removal and permission toggling

- Added functionality to remove members from a team and toggle their edit permissions.
- Introduced `remove_member_from_team` and `toggle_can_edit_team` API endpoints.
- Created `RemoveMemberDialog` component for user confirmation before removal.
- Updated `TeamMemberList` to include removal actions and permission toggling.
- Enhanced `FPTeam` model to manage team member permissions effectively.

* feat: add team details update functionality

- Implemented a new API endpoint `save` to update team fields, allowing modifications to `team_name` and `logo`.
- Created `ManageTeamHeader` component for editing team name and logo upload functionality.
- Integrated the new save functionality into the team store for seamless updates.
- Updated `ManageTeam` page to include the new header component for enhanced team management.

* refactor: clean up imports in team store

- Removed unused `call` import from the team store file to streamline dependencies.

* chore: better code

- Added checks to prevent duplicate team member invitations and ensure team owners cannot have their permissions toggled.
- Updated `FPTeam` model to prevent removal of the team owner from the team.
- Modified `RemoveMemberDialog` and `TeamMemberList` components to improve member removal functionality and user experience.

* fix: tests

* chore: better code
* feat: Image uploader component with crop

* feat: integrate ImageUploader for team logo management

- Replaced the existing logo upload button with an ImageUploader component for enhanced functionality.
- Added error handling and upload progress display within the ImageUploader.
- Updated the button label dynamically based on the upload state and existing logo presence.
- Added a search input to the TeamSwitcher component, allowing users to filter teams by name.
- Updated the team options computation to include search query handling.
- Integrated a new search icon and improved layout for better user experience.
- Refactored dropdown item templates to accommodate the search input and maintain consistent styling.
- Replaced FileUploader with ImageUploader component for improved logo management.
- Updated type handling for uploaded files to align with new component.
- Enhanced error handling and upload progress display in the logo upload section.
- Adjusted button label to reflect upload status dynamically.
- Modified the layout classes for improved design consistency and responsiveness.
- Changed the logout button icon from a string to a component reference for better integration with the icon library.
- Integrated Breadcrumbs component into the Dashboard for improved navigation.
- Defined breadcrumb items to enhance user context within the application.
* feat: add submissions page and update sidebar navigation

- Introduced a new "Manage Form Submissions" page for viewing form submissions.
- Updated the router to include a route for submissions.
- Refactored sidebar items to include navigation to the new submissions page.
- Enhanced the overview page with breadcrumb navigation for better user context.

* feat: submissions page v1

* feat: add Drawer component for UI enhancement

- Introduced a new Drawer component to provide a sliding panel interface.
- Implemented customizable properties for size, position, title, and actions.
- Added keyboard accessibility with Escape key support for closing the drawer.
- Included transition effects for smooth opening and closing animations.

* feat: extend Drawer component with additional size options

- Added "xl" size option to the Drawer component for enhanced customization.
- Updated size classes for both horizontal and vertical orientations to include new "xl" dimensions.

* feat: add submission details view and field value component

- Introduced SubmissionDetails component to display detailed information about a specific submission.
- Created SubmissionFieldValue component to render individual field values with appropriate formatting based on field type.
- Enhanced SubmissionList component to include a drawer for viewing submission details upon row click.
- Updated API integration to fetch submission data securely based on user permissions.

* feat: enhance SubmissionFieldValue component with dynamic class handling

- Added computed property to determine class names based on field type, improving layout for Switch and Checkbox types.
- Updated template structure to utilize dynamic classes for better styling and responsiveness.

* fix: validate doctype against linked_doctype in get_submission_response

A client could pass an arbitrary doctype to access submissions from a
different form. Now we fetch both linked_team_id and linked_doctype from
the Form doc and reject requests where the supplied doctype doesn't match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: throw when form not found in get_all_submissions

If form_id is invalid, frappe.db.get_value returns None and calling
has_permission(doc=None) has undefined behaviour. Explicitly throw
DoesNotExistError before the permission check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: show dash placeholder for null textarea value in SubmissionFieldValue

Textarea was rendering blank when value is null/undefined, inconsistent
with the default case which uses value ?? "–".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove unused isLoading ref in SubmissionList

isLoading was declared and set but never bound in the template, so the
loading state had no effect and was also never cleared on fetch error.
Removed it entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: show loading state in drawer when formData not yet available

If a row is clicked before formData loads, the drawer was empty. Now
shows a Loading... placeholder until linked_doctype is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add aria-labelledby to Drawer dialog element

Adds id="drawer-title" to the title heading and wires aria-labelledby
on the dialog div so assistive tech can announce the drawer title.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: form sharing methods

- Introduced `add_form_access` and `set_form_permission` functions to manage user permissions on forms.
- Updated the frontend to utilize the new API endpoints for adding access and setting permissions.
- Enhanced permission validation to ensure only authorized users can share forms.

* fix: validate permission_to allowlist and add docstrings to sharing API

- Validate `permission_to` against an explicit allowlist in `set_form_permission`
  to prevent unexpected kwargs from being forwarded to `add_docshare`
- Use `int(bool(value))` for safe coercion of the permission value
- Expand docstrings on `add_form_access` and `set_form_permission` with
  full Args/Raises sections and inline comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: update gitignore and pre-commit config for current project structure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: replace license.txt with LICENSE

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add community health files and GitHub templates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update README with contributing and security references

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: update Python version references to 3.14 across all configs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: set ruff target-version to py313 (py314 not yet supported)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](actions/cache@v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@v3...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [pre-commit/action](https://github.com/pre-commit/action) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/pre-commit/action/releases)
- [Commits](pre-commit/action@v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: pre-commit/action
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v3...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [jsdom](https://github.com/jsdom/jsdom) from 25.0.1 to 29.0.2.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](jsdom/jsdom@v25.0.1...v29.0.2)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 29.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [zod](https://github.com/colinhacks/zod) from 4.1.12 to 4.3.6.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](colinhacks/zod@v4.1.12...v4.3.6)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.3.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [vue](https://github.com/vuejs/core) from 3.5.21 to 3.5.32.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](vuejs/core@v3.5.21...v3.5.32)

---
updated-dependencies:
- dependency-name: vue
  dependency-version: 3.5.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
)

Bumps [socket.io-client](https://github.com/socketio/socket.io) from 4.8.1 to 4.8.3.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-client@4.8.1...socket.io-client@4.8.3)

---
updated-dependencies:
- dependency-name: socket.io-client
  dependency-version: 4.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.6 to 8.5.8.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.6...8.5.8)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The vulnerable dependency check has been extracted from the linter workflow into its own dedicated workflow file. This change improves organization and allows for more focused execution of dependency checks during pull requests and manual triggers.
harshtandiya and others added 23 commits May 1, 2026 20:51
* chore(deps): add torph

* refactor(editForm): expose isSaving and return promise from togglePublish

Consumers need to await the publish toggle to freeze UI state
until the server response settles. isSaving surfaces setValue.loading
so components don't reach into formResource internals.

* fix(FormBuilderHeader): prevent action button label from flickering

frappe-ui's setValue.submit mutates doc optimistically, making
isDirty briefly true before originalDoc syncs — causing two reactive
renders and two TextMorph transitions per click. Freeze the button
config on click and release only after the settled state is stable.

Also integrates TextMorph for animated label transitions and fixes
the :loading binding to reflect setValue state (not just fetch state).

* fix(FormBuilderHeader): add aria-label to action button for Playwright compatibility

TextMorph keeps exiting chars in DOM during animation, so Playwright
resolves the button's accessible name as merged old+new text. aria-label
bypasses inner text entirely, giving tests a stable selector.
Bumps [pinia](https://github.com/vuejs/pinia) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/vuejs/pinia/releases)
- [Commits](vuejs/pinia@v3.0.3...v3.0.4)

---
updated-dependencies:
- dependency-name: pinia
  dependency-version: 3.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.10 to 8.5.14.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.10...8.5.14)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
#120)

Bumps [autoprefixer](https://github.com/postcss/autoprefixer) from 10.4.21 to 10.5.0.
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](postcss/autoprefixer@10.4.21...10.5.0)

---
updated-dependencies:
- dependency-name: autoprefixer
  dependency-version: 10.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@lottiefiles/dotlottie-vue](https://github.com/LottieFiles/dotlottie-web/tree/HEAD/packages/vue) from 0.11.11 to 0.11.12.
- [Release notes](https://github.com/LottieFiles/dotlottie-web/releases)
- [Changelog](https://github.com/LottieFiles/dotlottie-web/blob/main/packages/vue/CHANGELOG.md)
- [Commits](https://github.com/LottieFiles/dotlottie-web/commits/@lottiefiles/dotlottie-vue@0.11.12/packages/vue)

---
updated-dependencies:
- dependency-name: "@lottiefiles/dotlottie-vue"
  dependency-version: 0.11.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat(FormField): add row_index and column_index for multi-column layout

Add row_index and column_index Int fields to FormField schema to support
side-by-side field placement. Both fields are layout-only and excluded
from DocType sync via to_frappe_field. Includes backfill patch (direct
SQL, idempotent) and tests covering sync regression and patch correctness.

* feat(store): add layout helpers and useGroupedRows composable

Add row_index/column_index to FormField type. Add moveField, insertNewRow,
and compact helpers to editForm store; update addField, addFieldFromDoctype,
and removeField to maintain layout invariants. Extract useGroupedRows as a
shared composable for use in both the builder and submission renderer.

* fix(store): guard addFieldFromDoctype, validate membership in move helpers

Add null guard to addFieldFromDoctype matching addField's pattern.
Add fs.includes() guard to moveField and insertNewRow to prevent
corrupting layout when a stale field ref is passed. Extract lastRowIndex
helper using reduce to avoid spread-on-large-array.

* feat(builder): row-based canvas render using groupedRows

Replace flat vuedraggable list with row/column grid derived from
useGroupedRows. Fields with the same row_index render side-by-side
as flex items. Extract FieldCard.vue from FormBuilderContent.
Drag-and-drop temporarily removed; restored in next chunk.

* fix(builder): stable v-for keys and restore row vertical spacing

Key rows by row_index value instead of array index to prevent wrong-row
DOM patching on delete. Key FieldCard by row_index+column_index instead
of idx which is undefined on new fields before save. Restore gap-3 between
rows to match previous my-3 spacing.

* feat(builder): restore drag-and-drop with nested vuedraggable

Replace flat draggable with per-row inner draggables using group="fields"
for cross-row drag. onFieldChange handles evt.moved (within-row reorder
via direct column_index renumber) and evt.added (cross-row via moveField).
Source row evt.removed is a no-op; target evt.added owns the move.

* feat(FormBuilder): row drop zones, eject-to-row, and layout fixes

- RowDropZone: thin drop target between rows; expands on drag-start so
  fields can be inserted between existing rows instead of merging as a
  new column. Uses an empty vuedraggable list with put=true/pull=false
  and clears its buffer on nextTick after emitting.

- Eject to own row: SquareSplitVertical button appears on FieldActions
  when a field shares its row with at least one other field. Clicking
  calls insertNewRow(field, row_index + 1), pushing the field to a new
  row immediately below.

- Code-review fixes: extract rowIndexOf() helper (eliminates repeated
  row[0]?.row_index ?? rIdx), remove outer wrapper div (key + classes
  moved to draggableComponent via <template v-for>), oldIdx guard now
  returns early instead of silently skipping the splice.

Note: cross-row visual swap requires the SortableJS Swap plugin which is
not bundled in vuedraggable 4.x — within-row reorder already behaves
correctly as a swap.

* feat(FormBuilder): hover highlight on drop zones, hide bottom zone when dragging last row

RowDropZone: MutationObserver watches for SortableJS placeholder insertion
to detect hover state (more reliable than pointer events during drag). Zone
expands and shows dashed border while dragging; turns blue when hovered.

FormBuilderContent: track draggingFromRow per @start event. Bottom drop
zone is hidden when the field being dragged is already from the last row
(dropping it there would be a no-op).

* feat(FormRenderer): row-based render with mobile stacking

Group submission fields by row_index/column_index via useGroupedRows.
Rows render flex-col on mobile, flex-row on md+. Use v-if on row + field
so conditionally hidden fields are unmounted (clears stale v-model state
and skips reqd computation).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(layout): column stacking via cell_index third axis

Add cell_index to FormField so multiple fields can stack vertically
inside a single column. Sort key becomes (row_index, column_index,
cell_index). Drag a field onto another column's cell list to drop
above/below existing cells; ColumnDropZone (vertical strip between
columns) handles new-column creation.

- Backend: cell_index Int + regenerated types + extended layout-not-
  synced test
- Composable: useGroupedRows returns FormField[][][] (rows -> cols ->
  cells)
- Store: 3-axis compact preserves multi-cell column grouping by
  remapping distinct column_index values per row; new insertCell;
  moveField/insertNewRow/addField set cell_index = 0
- FormBuilderContent: row -> column -> cell render with ColumnDropZone
  between columns; per-column draggable cell list (group "fields")
- FormRenderer: 3D render with v-if visibility per row/column/cell

CellDropZone deemed redundant — vuedraggable's native cell-list
insertion already shows the indicator and computes newIndex.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(FormBuilder): preserve last-row drag, polish drop zones

- v-show (was v-if) on trailing RowDropZone — keeps SortableJS instance
  alive across drag start, fixes last-row reorder
- global pointerup/dragend reset drag state when drop misses a target
- drop zones: subtle blue tint (was dashed border), gap-4 between cells

* fix(FormBuilder): sleek drop zones, no layout shift on drag

- outer drop zones reserve constant size (h-6 row, w-4 col); visual
  hairline rendered via ::before pseudo only
- removed parent flex gaps; zones now provide all inter-row/col spacing
- no flow shift when drag starts — form stays still
- highlighted state: 4px tinted bar, dragging: 1px hairline, idle: hidden

* fix(FormBuilder): scope drop zone transitions, respect reduced-motion

- before:transition-all → before:transition-[height,background-color]
  (row) / [width,background-color] (col): avoid animating unrelated props
- add before:ease-out for entrance feel
- motion-reduce:before:transition-none honors prefers-reduced-motion

* fix(FormBuilder): smooth drag reorder via SortableJS animation

Add :animation="150" to column draggable. SortableJS default 0ms made
sibling swaps instant, producing jittery snap-snap-snap during drag.
150ms shuffle animation smooths sibling reorder.

* feat(FormBuilder): add data-* test hooks and force pointer-event fallback

- FieldCard, RowDropZone, ColumnDropZone, FormBuilderContent, FormRenderer now
  expose stable data-form-builder-component and data-form-renderer-component
  selectors plus row/column/cell index attributes for layout introspection.
- Eject button on FieldActions tagged with a stable selector.
- Set forceFallback: true on the cell column and drop zone draggables so
  SortableJS uses pointer events instead of native HTML5 drag, making drag
  behavior deterministic in headless browsers (and unblocks Playwright).

* test(e2e): cover row/column/cell layout flows

Adds frontend/e2e/specs/form-layout.spec.ts with 8 scenarios:
1. Drag-stack a cell into an existing column.
2. Drop on a column drop zone creates a new column.
3. Drop on a row drop zone creates a new row.
4. Eject button moves a stacked cell to its own row.
5. Cross-row drag collapses the source column.
6. Within-column reorder renumbers cell_index.
7. Mobile viewport (375x800) stacks columns vertically (flex-col).
8. Conditional hide unmounts the column entirely (no empty gap).

Extends FormBuilderPage with layout introspection (rowCount,
columnCount, cellCount, fieldCard, waitForFields) and drag helpers
(dragFieldOntoCell, dragFieldToColumnZone, dragFieldToRowZone, ejectField).
Drag uses a real mouse path with intermediate steps; cross-row drags route
through an L-shaped waypoint via the row drop zone gap so SortableJS
doesn't accidentally swap into a neighbor column en route.

* docs(patch): note backfill predicate handles Int NOT NULL DEFAULT 0

Frappe's schema sync materializes new Int columns as NOT NULL DEFAULT 0,
so legacy FormField rows pre-dating row_index/column_index land on 0
after migration, never NULL. The existing != predicate is sufficient;
adding an IS NULL branch (per CodeRabbit suggestion) would be
unreachable. Comment captures the reasoning so future maintainers don't
re-litigate it.

* fix(FormBuilder): address review feedback on layout edges

Three changes from CodeRabbit pass:

- FormBuilderContent: trailing RowDropZone no longer hides when the drag
  starts in the last row. The previous guard (`draggingFromRow !== rowIndexOf(row, rIdx)`)
  blocked the only path to a new row below when dragging the only field
  in the last row. Removed the guard and the now-unused `draggingFromRow`
  ref.
- FieldCard / FieldActions: rename `isMultiColumn` -> `canEject`. The
  variable already returned true for stacked single-column rows (which
  is correct -- eject means "move to own row" and is valid for stacked
  cells), so the name was misleading. Behavior unchanged.
- FormField type contract: add `cell_index?: number`. The exported
  DocType interface was lagging behind the schema; consumers couldn't
  represent stacked-cell positions.

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- ColumnDropZone: reduced width from 4 to 3 for better alignment.
- RowDropZone: reduced height from 6 to 3 to enhance visual consistency.

These changes refine the appearance of drop zones during drag-and-drop interactions, ensuring a more cohesive user experience.
Cross-row drag waypoint was computed from card midpoints, which fell
outside the RowDropZone after its height shrank in 3c72b20, breaking
the cross-row drop tests. Anchor the transit waypoint to the actual
row-drop-zone whose centre lies in the gap between source and target
rows. Also harden the mobile-stack assertion with a networkidle wait.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Added a new skill, `/writing-tests`, to guide developers on using `frappe_factory_bot` for creating backend test fixtures in Forms Pro. The skill emphasizes the importance of using factories instead of direct document creation methods, providing rules and examples for authoring and consuming factories in tests. Updated CLAUDE.md to include this new skill and its usage guidelines.
The previous y-distance heuristic in dragFieldOntoCell compared the
vertical gap between source and target against sourceBox.height. After
3c72b20 shrank RowDropZone from h-6 (24px) to h-3 (12px), adjacent rows
sit close enough that the gap is smaller than a card's height, so
cross-row drags were misclassified as same-row. The helper then skipped
the intermediate waypoint, and the direct path from source to target
crossed the row drop zone, causing the drop to commit there and
fire insertNewRow instead of stacking into the target column.

Replace the heuristic with a direct comparison of data-row-index
attributes on the source and target FieldCards. Robust to any future
drop-zone size changes.
Adds lessons surfaced while building FormFactory / LinkedFormDoctypeFactory
for the export submissions feature.

Skill changes:
- New rule 5b: lazy-create related docs inside default_attributes
  (self.overrides.get(fk) or RelatedFactory.create().name), not only in
  traits. Required when an FK has no sensible literal default.
- New rule 7: do not override create() to wrap heavy side effects;
  build a separate factory for the dependency instead.
- Naming exception for doctypes used in narrow domain contexts (e.g.
  the generic DocType acting as a placeholder for a Form's linked
  DocType becomes LinkedFormDoctypeFactory).
- New 'Gotchas' section:
  * __del_override__ fires on garbage collection — chained
    Factory.create().name discards the doc, GCs it, and runs cleanup
    before the FK is used downstream.
  * before_insert / validate hooks can clobber factory overrides
    (e.g. Form.before_insert forces is_published=False); patch
    post-insert and save again.
- Reference list now points at form_factory / linked_form_doctype_factory
  as the pattern for doctypes with non-trivial dependencies.

Factory changes:
- fp_team_factory, user_factory, user_invitation_factory now use
  BaseFactory[T] generic typing as the skill describes (previously
  the skill claimed it but the factories did not).
* feat(api): export submissions as CSV/Excel

Adds forms_pro.api.export.export_submissions, a whitelisted method that
streams submissions of a Form as CSV or Excel via Frappe's DataExporter.

Authorization is gated on Form.write; the linked DocType's export perm
is intentionally bypassed by a scoped set_user(Administrator) swap.
saved_user / saved_sid / saved_data are snapshotted and restored in
finally so the response cookie stays valid (set_user is otherwise built
for background jobs, not web requests).

Other notes:
- Filter on fp_linked_form so a form does not leak rows of other forms
  sharing the same linked DocType.
- Validate file_type at runtime (Literal is static-only).
- Skip display-only form fields (Heading 1/2/3, etc.) via a new
  FormField.stores_value property derived from frappe.model.no_value_fields.
- secure_filename + timestamp for the download filename.
- Access Log row written under the real user before the privilege swap.

* feat(ui): add Export dropdown to SubmissionList

CSV and Excel options trigger a real browser navigation to the export
API so Frappe sends Content-Disposition: attachment and the file
actually downloads (an XHR via createResource would just return the
body as JSON and never trigger a download).

* test(export): cover dispatch, scoping, perms, audit, session restore

Adds two factories and an integration test module for export_submissions:

- LinkedFormDoctypeFactory: builds a placeholder custom DocType
  pre-wired with fp_submission_status and fp_linked_form, so a Form
  pointing at it accepts submissions immediately.
- FormFactory: standard frappe_factory_bot pattern; default attrs
  honor self.overrides.get(...) for linked_doctype / linked_team_id
  to avoid orphaning related records.

test_export covers:
- file_type dispatch (Excel != CSV) and runtime validation.
- fp_linked_form scoping across two forms on the same linked DocType.
- CSV header carries form field labels; display-only fields excluded.
- Permission gate rejects users without Form.write.
- Access Log row written with the real user attribution.
- Session user restored on success and when DataExporter raises.

* fix(export): address Semgrep + CodeRabbit findings

- Suppress the frappe-semgrep-rules.rules.security.frappe-setuser
  blocking rule on both set_user calls with inline nosemgrep markers
  and a manual-audit comment. Authorization is gated on
  has_permission('Form', 'write', form_id); the swap exists only to
  bypass DataExporter's can_export on the linked DocType, which Forms
  Pro intentionally does not grant at the role level. Session state
  is snapshotted and fully restored.
- Re-raise on session-restore failure. Previously a failure inside
  set_user(saved_user) was logged and swallowed, which would leave the
  request running as Administrator while sid/data were already
  restored. Now: log, complete the sid/data restore, then re-raise so
  the request never proceeds with elevated privileges.
- Guard the export trigger in SubmissionList.vue against a missing
  currentFormId; show a toast and abort instead of POSTing an empty
  form_id to the backend.

* revert(export): drop restore_failure re-raise plumbing

Reverts the re-raise scaffolding from the CodeRabbit fix. set_user
itself raising is vanishingly rare and the log_error already surfaces
it; the extra ceremony added more noise than safety. sid/data restore
still runs in finally.

* chore: sanitize code
Bumps [vue-tsc](https://github.com/vuejs/language-tools/tree/HEAD/packages/tsc) from 3.2.4 to 3.2.8.
- [Release notes](https://github.com/vuejs/language-tools/releases)
- [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/language-tools/commits/v3.2.8/packages/tsc)

---
updated-dependencies:
- dependency-name: vue-tsc
  dependency-version: 3.2.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Harsh Tandiya <harsh.tandiya@gmail.com>
Bumps [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) from 1.62.0 to 1.64.0.
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.64.0/npm/oxlint)

---
updated-dependencies:
- dependency-name: oxlint
  dependency-version: 1.64.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Harsh Tandiya <harsh.tandiya@gmail.com>
…128)

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.6.0 to 25.7.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Harsh Tandiya <harsh.tandiya@gmail.com>
* fix(builder): replace live previews with icon palette to kill autofill

* test(e2e): cover Add Fields palette layout, search, and add-to-canvas

* polish(builder): a11y + press feedback on Add Fields palette buttons

* polish(builder): tooltip on palette buttons explaining click action

* polish(builder): fix scale transition and drop redundant tooltip on palette buttons

- transition-colors → transition-all so active:scale-[0.98] animates
- remove constant-text Tooltip wrapper (icon + label already convey action)
- drop redundant `as Fieldtype` cast and unused Fieldtype import
- type-only import for FormFields

* polish(builder): a11y + semantic colors on sidebar tabs

- tablist semantics on rail (role=tablist/tab/tabpanel, aria-selected, aria-controls)
- aria-label on icon-only tab buttons (SR-accessible)
- left accent bar on active tab for stronger emphasis (bg-surface-gray-7)
- section-fade transition (120ms ease-out enter, ease-in leave) on content panel
- respect prefers-reduced-motion
- 100vh → 100dvh on sidebar height (mobile-safe)
- swap to frappe-ui semantic tokens: bg-surface-gray-{1,2,3}, bg-surface-white, border-outline-gray-{1,2}
- default active section via id lookup, sidebarSections const (never mutated)
frappe-ui Rating uses 1..N stars; Frappe stores Rating as a 0..1 fraction
and clamps to [0, 1]. Without conversion, clicking N stars stored 1.0 and
on reload only the first star rendered, losing the real selection.

Wrap frappe-ui Rating in a component that converts 0..1 <-> 1..5 in both
directions, and scale the value in the read-only submission view.
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.59.1 to 1.60.0.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](microsoft/playwright@v1.59.1...v1.60.0)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  dependency-version: 1.60.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Updates the requirements on [faker](https://github.com/joke2k/faker) to permit the latest version.
- [Release notes](https://github.com/joke2k/faker/releases)
- [Changelog](https://github.com/joke2k/faker/blob/v40.18.0/CHANGELOG.md)
- [Commits](joke2k/faker@v40.13.0...v40.18.0)

---
updated-dependencies:
- dependency-name: faker
  dependency-version: 40.18.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Harsh Tandiya <harsh.tandiya@gmail.com>
Bumps [@vueuse/core](https://github.com/vueuse/vueuse/tree/HEAD/packages/core) from 14.2.1 to 14.3.0.
- [Release notes](https://github.com/vueuse/vueuse/releases)
- [Commits](https://github.com/vueuse/vueuse/commits/v14.3.0/packages/core)

---
updated-dependencies:
- dependency-name: "@vueuse/core"
  dependency-version: 14.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Harsh Tandiya <harsh.tandiya@gmail.com>
Bumps [vue-router](https://github.com/vuejs/router) from 4.5.1 to 4.6.4.
- [Release notes](https://github.com/vuejs/router/releases)
- [Commits](vuejs/router@v4.5.1...v4.6.4)

---
updated-dependencies:
- dependency-name: vue-router
  dependency-version: 4.6.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Harsh Tandiya <harsh.tandiya@gmail.com>
#142)

* feat(perms): add require_permission decorator

* refactor(api): split form.py into form/ package

* refactor(api): split team/submission/user/export/settings into packages

Extract pydantic schemas into per-package schema.py modules where applicable
(submission, user). team/export/settings have no inline schemas. Each
package __init__.py explicitly re-exports endpoints so the
forms_pro.api.<family>.<endpoint> whitelist URLs continue to resolve.

Also update test_submission_validation.py to import private helpers
from forms_pro.api.submission.endpoints (the public __init__.py exposes
only whitelisted endpoints).

* test: relocate tests to per-package and doctype folders

- test_form_field.py → doctype/form_field/ (Frappe doctype-test convention).
- test_invitations.py → api/team/, test_submission_validation.py → api/submission/,
  test_export.py → api/export/ (co-located with their endpoint families).
- test_roles.py and tests/factories/ stay central (cross-cutting).

Also update test_export.py's monkeypatch path to
forms_pro.api.export.endpoints.DataExporter — the public __init__.py
no longer re-exports the imported symbol.

* feat(api): add get_form_for_view with read permission gate

* feat(api): add get_form_for_edit with write permission gate

* feat(api): add get_team_for_manage with read permission gate

* refactor(api/form): retrofit endpoints with require_permission

Apply @require_permission decorator to get_form_shared_with,
remove_form_access, add_form_access, set_form_permission. Removes the
manual frappe.has_permission + frappe.throw block in each. Behaviour
diffs:

- Missing form now raises DoesNotExistError (HTTP 404). Previously the
  endpoint would fall through to a downstream LinkValidationError or a
  ValidationError-as-deny via has_permission(missing).
- get_form_shared_with and remove_form_access now raise PermissionError
  (HTTP 403) on deny. Previously both raised the default ValidationError
  (HTTP 417) because frappe.throw was called without an exc class.

Same Form, same ptype, same docname source — the decorator is a strict
behaviour-preserving wrapper save for the two corrections above.

* refactor(api/team): retrofit endpoints with require_permission

Apply @require_permission to get_team_members, invite_team_members,
toggle_can_edit_team, save, and remove_member_from_team. Each preserves
its existing doctype + ptype semantics. Adds HTTP 404 on missing team
via the decorator's existence check (previously a downstream Frappe
LinkValidationError or DoesNotExistError from frappe.get_doc).

Skipped (intentionally): create_team (no docname yet), switch_team
(session mutation; existing read check kept inline), get_team_forms
(no current gate), add_member_to_team_via_invitation (invitation-token
auth model differs).

* refactor(api/submission): retrofit list endpoints with require_permission

Apply @require_permission("Form", "read", param="form_id") to
get_user_submissions and get_all_submissions. Drop manual FP Team
write check + 404 throw from get_all_submissions, and the dead
Guest early-return from get_user_submissions (endpoint is not
allow_guest).

get_submission and get_submission_response retain their manual
permission checks: their target doctype is dynamic (passed as a
parameter), which the current decorator (fixed doctype string)
cannot express.

Adds forms_pro/api/submission/test_submission.py with allow/403/404
coverage for both retrofitted endpoints.

* refactor(api/export): retrofit export_submissions with require_permission

Apply @require_permission("Form", "write", param="form_id") to
export_submissions and drop the manual frappe.has_permission block.
Privilege-swap behavior, audit log, and session restoration remain
unchanged.

Tightens TestExportPermissions to assert http_status_code == 403 on
the deny path and adds an explicit 404 case for a missing form.

* test(api/user): add coverage for current user + teams endpoints

api/user endpoints (get_user, get_current_user, get_user_teams) gate
on session state, not DocShare, so they take no @require_permission
decorator. Adds an integration test file that pins their current
behavior so future changes surface regressions:

- get_user returns a basic payload for an existing user, None for a
  missing user.
- get_current_user returns the session user's profile under set_user.
- get_user_teams returns a list for a real user and [] for Guest.

* feat(perms): add routeData store + useRouteData composable

Introduces the frontend plumbing for route-level permission resolution:

- src/types/router.d.ts augments vue-router's RouteMeta with optional
  allowGuest and fetch fields so per-route data resolvers are typed.
- src/stores/routeData.ts is the single Pinia store driving navigation:
  state {status, data, error}, plus resolve(route) which awaits the
  meta.fetch resource and normalizes Frappe errors (exc_type,
  HTTP status, messages) into a uniform RouteError shape.
- src/composables/useRouteData.ts exposes typed, computed accessors so
  pages can read status/data/error without touching the store directly.

No router/guard wiring yet, that lands in D2.

* feat(perms): add per-route fetch meta + route data guard

Wires the gated pages to the routeData store introduced in D1:

- Three resource factories (formForView, formForEdit, teamForManage)
  point at the new whitelisted endpoints. cache keys per id keep
  intra-session refetches cheap.
- meta.fetch attached to "Manage Team", "Manage Form" (parent), and
  "Edit Form". For nested Manage Form children (Overview, Submissions)
  vue-router merges parent meta into the matched route, so the parent
  resolver applies to every sub-tab.
- beforeEach simplified to return-style, gains a call to
  useRouteData().resolve(to) after the auth check. Login redirect and
  allowGuest paths preserved.

Public submission routes keep their existing beforeEnter and stay
guest-friendly; no fetch attached.

Manual error UX (RouteError component, page integration) lands in D3;
until then a denied page will surface the store's error state but
without a styled fallback.

* feat(perms): RouteError component + page integration + nav indicator

Closes the user-visible half of the permission system.

- components/RouteError.vue: one component, titled per exc_type
  (PermissionError → Access Denied, DoesNotExistError → Not Found,
  AuthenticationError → Login Required, fallback → Something Went
  Wrong). Carries an optional first-line message and HTTP status,
  plus a Go to Dashboard button.
- App.vue: global LoadingIndicator (top-right) bound to
  routeData.isNavigating so users see in-flight perm resolution.
- pages/manage/ManageForm.vue, pages/EditForm.vue,
  pages/team/ManageTeam.vue: render <RouteError> when status === 'error',
  existing layout otherwise.
- stores/form/manageForm.ts: dropped the primary useDoc fetch. The Form
  document now flows in via routeData (resolved by the guard against
  get_form_for_view); manageFormStore.formData / formFields / formOwner
  read from there. Sharing mutations and formAccessResource untouched.
- pages/manage/overview/Overview.vue: removed the now-dead
  formResource.loading branch (loading is handled globally).

* chore: refactor page loading indicator

* fix(fp_team): coerce can_edit_team to bool when no DocShare exists

team_members property used the raw return of frappe.db.get_value("DocShare", share_name, "write"), which is None when no DocShare row exists for a member, in turn making GetTeamMembersResponse.model_validate raise a pydantic ValidationError ("Input should be a valid boolean").

Coerce to bool() and short-circuit when share_name itself is None. False is the correct semantic when no share exists, since the user has no write permission on the team.

* fix(perms): await full user init before resolving route fetch

Race condition: meta.fetch for /team builds the get_team_for_manage
resource using useUser().currentTeam?.name. Until userTeamsResource
finishes, currentTeam is null, so the resource fires with an empty
team_id and require_permission's existence check throws 404.

- stores/user.ts: cache the initialize() promise so repeated calls
  share the same in-flight fetches instead of racing.
- router.ts: after the userResource check confirms login, await
  useUser().initialize() inside beforeEach so userTeams + currentTeam
  are populated before the route resolver runs.

Login redirect path is unaffected: initialize() is only awaited when
isLoggedIn is true, and a thrown error still flips isLoggedIn to false
via the existing catch.

* test(e2e): cover route-level permission gates

Two Playwright specs exercising the meta.fetch + RouteError pipeline
through a real browser against the built SPA:

1. /forms/manage/<bogus-id> drives a 404 on get_form_for_view and
   asserts the RouteError "Not Found" heading renders.
2. The logged-in test user creates and opens their own form's
   /edit-form/:id, the get_form_for_edit call returns 200, and no
   RouteError heading appears.

Read-only viewer → "Access Denied" (403) is deferred: it needs an
admin-owned form shared read-only with the test user, which requires
a second authenticated API context that the current fixture set
does not provide. Inline comment notes this for follow-up.

* fix(perms): render allowed inline tags in RouteError message

Backend permission messages embed <strong> tags around the user email.
The template escaped them so they showed literally. Escape all HTML then
re-allow a safe set of inline tags (strong/b/em/i) and render via v-html,
keeping bold formatting without an XSS vector.
@harshtandiya harshtandiya marked this pull request as ready for review May 24, 2026 09:22
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c2e8529-6bdf-4505-99d0-266658adcc2f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/backport-develop-to-version-15

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Pin Frappe to >=15.0.0,<16.0.0; Python >=3.10; Faker ~=38.2.0; ruff target py310
- Set app version to 15.0.0
- Retarget CI/linter/typecheck/ui-tests workflows to version-15 (Python 3.10, Node 20, MariaDB 10.6.24, bench init --version version-15); drop merge_group triggers
- Import frappe.tests.utils.FrappeTestCase aliased as IntegrationTestCase for v15 compatibility (matches version-15 convention)
@harshtandiya harshtandiya force-pushed the chore/backport-develop-to-version-15 branch from 8da7879 to 36bda6f Compare May 24, 2026 09:30
@harshtandiya harshtandiya merged commit 7dd35fa into version-15 May 24, 2026
7 checks passed
@harshtandiya harshtandiya deleted the chore/backport-develop-to-version-15 branch May 24, 2026 09:35
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