fix image stale-render on in-place overwrite (T-312)
Image cards, thumbnails, the lightbox, and `clide image show` rendered via Image.file, whose FileImage keys Flutter's imageCache by (path, scale) only — so overwriting a file at the same path handed back the previously decoded frame (hit live re-exporting a wireframe PNG). Add ClideFileImage, a FileImage that folds mtime + size into ==/hashCode so an in-place change is a fresh cache key (miss → re-decode), and route the five Image.file sites through it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,6 @@ library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:clide/builtin/claude/src/activity_cluster.dart';
|
||||
import 'package:clide/builtin/claude/src/conversation_card.dart';
|
||||
@@ -499,8 +498,8 @@ class _ConversationTurn extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 360),
|
||||
child: Image.file(
|
||||
File(m.path),
|
||||
child: Image(
|
||||
image: ClideFileImage(m.path),
|
||||
fit: BoxFit.contain,
|
||||
alignment: Alignment.centerLeft,
|
||||
errorBuilder: (_, __, ___) => _imagePlaceholder(m.path),
|
||||
@@ -522,8 +521,8 @@ class _ConversationTurn extends StatelessWidget {
|
||||
ClideKernel.of(context).dialog.show<Object>(
|
||||
(ctx, dismiss) => ClideLightbox(
|
||||
onDismiss: dismiss,
|
||||
child: Image.file(
|
||||
File(path),
|
||||
child: Image(
|
||||
image: ClideFileImage(path),
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (_, __, ___) => _imagePlaceholder(path),
|
||||
),
|
||||
|
||||
@@ -378,7 +378,7 @@ class ClaudeExtension extends ClideExtension {
|
||||
_ctx?.dialog.show<Object>(
|
||||
(c, dismiss) => ClideLightbox(
|
||||
onDismiss: dismiss,
|
||||
child: Image.file(File(path), fit: BoxFit.contain),
|
||||
child: Image(image: ClideFileImage(path), fit: BoxFit.contain),
|
||||
),
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
/// A bounded, keyboard-activatable thumbnail that opens the full image in the
|
||||
/// shared lightbox (T-252) on tap. Used both for `@<path>` image tokens in the
|
||||
/// conversation log and for the composer's attachment chips, so the two stay
|
||||
/// visually consistent. Reads the file directly via `Image.file` (dart:io) —
|
||||
/// pasted temp files live outside the workspace, so this is not gated by the
|
||||
/// `files.read` allow-list (D-80); it's display-only. A missing/unreadable file
|
||||
/// degrades to a muted placeholder instead of throwing.
|
||||
/// visually consistent. Reads the file directly via [ClideFileImage] (which
|
||||
/// re-decodes when the file changes in place, T-312) — pasted temp files live
|
||||
/// outside the workspace, so this is not gated by the `files.read` allow-list
|
||||
/// (D-80); it's display-only. A missing/unreadable file degrades to a muted
|
||||
/// placeholder instead of throwing.
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -20,8 +19,8 @@ void openImageLightbox(BuildContext context, String path) {
|
||||
ClideKernel.of(context).dialog.show<Object>(
|
||||
(ctx, dismiss) => ClideLightbox(
|
||||
onDismiss: dismiss,
|
||||
child: Image.file(
|
||||
File(path),
|
||||
child: Image(
|
||||
image: ClideFileImage(path),
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (ctx, _, __) => _placeholder(ctx, 48),
|
||||
),
|
||||
@@ -65,8 +64,8 @@ class ImageThumbnail extends StatelessWidget {
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
child: Image.file(
|
||||
File(path),
|
||||
child: Image(
|
||||
image: ClideFileImage(path),
|
||||
width: size,
|
||||
height: size,
|
||||
fit: BoxFit.cover,
|
||||
|
||||
Reference in New Issue
Block a user