Published 2026-05-15

CanvasModel.toMap: Removed unnecessary .toList().first replacing with .first (TASK-056).ElementProperties.fromMap: Removed redundant .toList() applied after List.from() which natively returns a list (TASK-057).CanvasModel JSON serialization calls: Removed unnecessary eager .toList() conversions before map entries assignment (TASK-059).ElementModel.getSnap: Refactored findNearestSnap to directly accept an Iterable, avoiding an intermediate mapping list allocation (TASK-058).SizeHandlePainter.shouldRepaint: now compares relevant fields so corner and side grippers repaint appropriately upon canvas zoom or configuration change (TASK-043).PageLayout._layout(): added guard for empty page lists, preventing Bad state: No element crashes when computing row heights (TASK-044).PageLayout._layout(): fixed row-wrap right-edge calculation to use logical rect.right instead of relative rect.width avoiding wasted pagination space (TASK-045).PageLayout._layout(): added explicit block to gracefully lay out oversized pages instead of skipping them incorrectly (TASK-046)._renderSelectedPageOnTop(): safely wrapped getPage()! lookup yielding early return avoiding crash during selection detachment (TASK-047).ScriberoProperties: introduced _selectedPage check ensuring graceful degradation preventing properties layout sidebar crash on decoupled elements (TASK-048).doDemoTransparentPlaceholder: handled empty array conditions ensuring scripting demo falls back accurately without throwing null exceptions on index access (TASK-049).doPrijsDoorhaling(): validated the initial presence of text offsets shielding scripting routines against early empty string formatting attempts (TASK-050).ScriberoInterpolationProvider._replaceField(): replaced infinite while loop text matching ensuring repeated variable field content templates execute safely (TASK-051)._ON_RestoreImageAspectRatioAction: correctly shielded imageSize zeros eliminating divisions by zero crashing aspect ratios when decoding faulty images (TASK-052)._toPdflibImageFitOptions: implemented validations intercepting missing zero scaling targets thus mitigating PDF stream block corruption errors (TASK-053).doDemoTransparentPlaceholder: checked size structures removing unsafe unwrap evaluations stopping script calculation crashes upon unmounted parent constraints (TASK-054).ScriberoObject: ensured layout limits by clamping padding zoom multiples averting unbounded dimension layouts crashing zero width calculations (TASK-055).SelectionController.select(): replaced force-unwrap navigatorKey.currentContext! inside a scheduled microtask with a null-safe guard that logs a warning and returns early when the widget tree is disposed, matching the pattern used in setChildren (TASK-023), _selectIdsAfterCanvasLoad (TASK-033), and ScriberoImage (TASK-036).SelectionController._updateChildOrder: added null guard for element.parent at the top of the method; logs a warning and returns early instead of throwing Null check operator used on a null value when the element has no parent.SelectionController._wrapInitialMultipleSelection / _wrapExtendedMultipleSelection: added anchors.isEmpty guard before reduce(math.min/max) calls; logs a warning and returns early instead of throwing StateError: No element when all selected elements have zero-area bounding boxes.RotateHandlePainter.shouldRepaint: was always returning false, causing the rotation handle circle and line to freeze at the scale in effect on first paint and never react to canvas zoom changes; now compares scale so the handle repaints correctly on every zoom change.PdflibPrimitives (pdflibCircle, pdflibEllipse, pdflibImage, pdflibLine, pdflibPage, pdflibPlaceholder, pdflibRectangle, pdflibText, pdflibTriangle): guarded every fillColors.first access with fillColors.isNotEmpty && and replaced fillTints.first with fillTints.firstOrNull ?? 0xFF; prevents StateError: No element during PDF export when an element is constructed from a legacy or hand-crafted JSON document that omits fill lists.PdflibPrimitives._pdflibImageOptions (BoxFit.none branch): removed erroneous .pt2mm() call on imageSize (which was already in points) and converted panOffset (stored in mm) to points via .mm2pt() before using it in the matchbox clip-rect calculation; clip coordinates were previously off by ~2.835× due to the unit mismatch.ElementSelectHandler._ON_TapUpOnElement: replaced three consecutive model.getPage()! force-unwraps with a single null-safe lookup; returns early when the element has no page ancestor instead of throwing, and eliminates the redundant ancestor-walk cost.GridPainter.shouldRepaint: was always returning false, causing the grid element's guide lines to freeze at the opacity in effect on first paint and never react to selection changes; now compares scribero, model, and scribero.selected so lines repaint correctly on every relevant state change.ElementSizeHandler (bottom-centre, right-centre, top-centre grippers): snap correction was applied after the kScriberoMinimumElementSize clamp, allowing a negative snap delta to push the size below the intended floor; a second clamp is now applied after the snap offset.ImagePainter.paint (BoxFit.none branch): source rectangle size was built with size.mm2pt(), a PDF-unit conversion that inflated the logical-pixel widget size by ~2.835× and caused the canvas preview to show the wrong crop region; replaced with cropRect.size which uses the correct image-pixel dimensions.CirclePainter.paint / TrianglePainter.paint: added early-return guard when the locally-built fillColors list is empty (occurs after model.fillColors is empty); prevents StateError: No element when .first was called on an empty list.SelectionController._selectIdsAfterCanvasLoad: replaced force-unwrap navigatorKey.currentContext! inside an asynchronous CanvasLoadReadyEvent listener with a null-safe check that logs a warning and returns without crashing when the widget tree is disposed.CanvasModel.parseScriberoElements: replaced ElementType.values.byName(e['type']) with firstWhere(... orElse: () => ElementType.none) so that a missing or unrecognised type key in a child map is treated as ElementType.none instead of throwing TypeError / ArgumentError.SelectionController._wrapInitialMultipleSelection / _wrapExtendedMultipleSelection: added null-safe getPage() lookup with an early-return warning at the top of each method; prevents Null check operator used on a null value when either is called after the selection loses its page ancestor due to a concurrent state change.ScriberoImage._doDetermineRealImageSize: replaced force-unwrap navigatorKey.currentContext! inside a Future.microtask with a null-safe guard matching the pattern used in ElementModel.setChildren (TASK-023); prevents a crash when the image element is unmounted between decode completion and microtask execution.SelectedPainter.shouldRepaint: was always returning false, causing the scale-dependent selection border to freeze at the zoom level in effect when the element was first selected; now compares scale so the border repaints correctly on every canvas zoom change.ElementModel.setChildren: replaced force-unwrap navigatorKey.currentContext! inside the scheduled microtask with a null-safe check; no exception is thrown when the navigator context is unavailable (startup, hot-restart, or disposed tree).SelectionController.unwrapContainer: replaced force-unwrap container.getPage()! with a null-safe lookup; logs a warning and returns early when the container has no page ancestor, preventing a crash during concurrent reload or scripted ungroup on a detached element.ElementSizeHandler._ON_PointerMoveTopRight (top-right gripper): snap correction now only advances position.dy (top edge); position.dx (left edge) is no longer shifted by snap.dx, eliminating the unintended horizontal translation that moved the element while also growing its width.ElementModel.canMoveToPage / moveToPage: added scribero.elementModels.isEmpty guard before .first access; returns false / exits early during transient canvas-reload windows when the root element list is empty, preventing StateError: No element.PdflibPrimitives._parseDefaultTextFormatting: added textPdflib.isEmpty early-return guard; text elements with no committed content (newly added, cleared, or loaded from a legacy document) no longer throw StateError: No element during PDF export.GuideLinesPainter.shouldRepaint: was always returning false, freezing guide lines during drag; now compares relevant fields so lines repaint correctly on move, size, select, snap, and rotate.ElementModel.updateWithoutTriggering: wrapped callback in try/finally so any exception thrown by the callback cannot permanently leave _updating > 0 and silence all future notify() calls.SelectionController._wrapExtendedMultipleSelection: replaced force-unwrap children[id]! with null-safe lookup; stale childOrder entries are skipped with a warning instead of crashing; orphaned children not in childOrder are appended with a warning.PdflibPrimitives.pdflibCircle: arc radius now uses math.min(dx, dy) instead of only the x-axis half-width, so non-square circle elements preserve their correct radius in PDF export.ElementSizeHandler (right/left centre grippers): grippers now fix the midpoint of the correct edge (_updateForCentreLeftAnchor / _updateForCentreRightAnchor) instead of a corner, eliminating y-axis drift when resizing rotated elements from the side.PdflibPrimitives.pdflibText: guarded .first access on textPdflib and .last access on textOffsetsY with isNotEmpty checks; empty lists (element not yet rendered or loaded from a legacy document) no longer throw StateError and abort PDF export.ElementModel.getAnchorCoords: replaced force-unwrap getPage()! with a null-safe lookup; returns an empty AnchorCoords when no page ancestor exists instead of crashing during snap/guide-line painting.SelectionController._updateMultipleSelection: replaced force-unwrap getPage()! with a null-safe lookup; logs a warning and returns early when the first selected element has no page ancestor.PdflibPrimitives._pdflibImageOptions (BoxFit.none branch): removed always-true dead ternary imageFit == BoxFit.none ? imageOffset : Offset.zero; renamed local variable to panOffset to avoid shadowing ElementModel.offset.ElementModel.getSnap: unnecessary clamp in findNearestSnap replaced with correct distance.abs() <= kScriberoSnapTolerance guard.ElementModel.keepOnPage: refactored four-sort-pass approach to a single reduce-based min/max check; documented intentional "all-corners-off" semantics.CanvasModel._collectUsedColors / _collectUsedFonts: null-safe iteration of children key prevents crash when serialising leaf elements to map or PDF.SelectionController.lowerElements / lowerElementsToBottom / raiseElements / raiseElementsToTop: added isEmpty guard to prevent StateError when called with no selection.SelectionController.select: replaced force-unwrap of parent! with null-safe check for the cross-page deselect guard.SelectionController.selectById: replaced force-unwrap of objectModels[id]! with a null-safe lookup that logs a warning and returns gracefully on missing ID.ElementRotateHandler: divided rotation drag delta by scribero.scale so rotation speed is zoom-invariant, matching the move handler.ElementSizeHandler: snap delta and aspect-ratio enforcement are now mutually exclusive in all four corner grippers — AR lock skips snap to prevent height/width ratio drift.CanvasPointerHandler: scroll zoom step is now proportional to the scroll delta magnitude (clamped via _kMinScrollDelta / _kMaxScrollDelta / _kMaxZoomStep) instead of a fixed 0.1 per event.ElementModel.toMap: default-value pruning uses DeepCollectionEquality instead of toString() comparison, fixing fragile equality for floats, Colors, and nested collections.ElementModel.moveToPage: target offset uses the unrotated offset + parent.offset (not the rotated corners.first) so reparented rotated elements land at the correct position.SelectionController.unwrapContainer / _updateChildOrder: replaced assert(children.isEmpty) + throw Exception with a warning log + safe append of orphaned children, preventing silent data loss in release builds.spider.yaml with flutter_gen for asset constants.get_it dependency.BuildContext in providers.Bad state, no element crash when there were empty text elements.Builder where needed.OK buttons based on valid/default state.ScriberoInterpolationProvider reverted.ScriberoInterpolationProvider now takes values from record[key] ?? record['metadata'][key].id properties.angle_property.dartend_angle_property.dartfill_alpha_property.dartfill_tint_property.dartimage_alpha_property.dartimage_offset_left_property.dartimage_offset_top_property.dartimage_page_index_property.dartimage_scale_property.dartname_property.dartoffset_left_property.dartradius_property.dartoffset_top_property.dartscale_property.dartsize_height_property.dartsize_width_property.dartspacing_property.dartstart_angle_property.dartstatic_text_property.dartstroke_alpha_property.dartstroke_tint_property.dartstroke_width_property.dartElementProperties.fromMap when doubles were mangled to int when set from javascript.ScriberoInterpolationProvider::onInterpolate now needs a BuildContext.ScriberoScriptingProvider::onBeforeRender now needs a BuildContext.ScriberoScriptingProvider::onAfterRender now needs a BuildContext.imageId is now dynamic everywhere.RepaintBoundary, so every update of a widget somewhere unrelated on the screen caused the painter to paint.ScriberoFab already had this RepaintBoundary.async constructs.provider dependency.print statements for logging.finals due to new linter rules.tp_open_file.file_picker to 5.0.0.