diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 7714881..ae4965c 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -25,7 +25,7 @@ {"id":"smartfiles-u4i","title":"Apply consistent tag styling between filter and document views","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T21:49:21.617484+01:00","created_by":"abedurftig","updated_at":"2026-01-11T21:53:15.289719+01:00","closed_at":"2026-01-11T21:53:15.289719+01:00","close_reason":"Closed"} {"id":"smartfiles-vap","title":"Missing Platform.runLater in ApplicationInteractor event handlers","description":"In ApplicationInteractor.java, most event handlers correctly use Platform.runLater() to update the model on the JavaFX Application Thread. However, two handlers do not:\n\n- handleDocumentTagAddedEvent (line 49-51) calls model.updateDocumentTags() directly\n- handleArchiveEntryAddedEvent (line 61-64) calls model.addDocumentFromArchiveEntry() directly\n\nSince Spring events can be published from any thread, these could cause UI updates from non-FX threads, leading to race conditions or crashes.\n\n**Fix:** Wrap these calls in Platform.runLater().","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-01-15T22:32:45.051937+01:00","created_by":"abedurftig","updated_at":"2026-01-16T20:58:09.470451+01:00","closed_at":"2026-01-16T20:58:09.470451+01:00","close_reason":"Closed"} {"id":"smartfiles-w73","title":"Maven compiler plugin source/target mismatch","description":"In pom.xml, the properties define java.version=25 and maven.compiler.source/target=25, but the maven-compiler-plugin configuration explicitly sets source=22 and target=22 (lines 129-130), contradicting the properties.\n\n**Fix:** Remove explicit source/target from plugin config to use the property values, or update to match.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-01-15T22:36:17.931659+01:00","created_by":"abedurftig","updated_at":"2026-01-16T21:17:57.858516+01:00","closed_at":"2026-01-16T21:17:57.858516+01:00","close_reason":"Closed"} -{"id":"smartfiles-we7","title":"Display document details dateLastModified and dateCreated in the document details","description":"Display the dateCreated and dateLastModified of the selected document in the document details in the bottom right.","status":"open","priority":2,"issue_type":"feature","created_at":"2026-01-07T21:31:02.161058+01:00","created_by":"abedurftig","updated_at":"2026-01-07T21:32:30.284216+01:00"} +{"id":"smartfiles-we7","title":"Display document details dateLastModified and dateCreated in the document details","description":"Display the dateCreated and dateLastModified of the selected document in the document details in the bottom right.","status":"in_progress","priority":2,"issue_type":"feature","created_at":"2026-01-07T21:31:02.161058+01:00","created_by":"abedurftig","updated_at":"2026-01-19T23:07:46.174507+01:00"} {"id":"smartfiles-wyr","title":"Window size and position not persisted","description":"The application starts with fixed dimensions (1024x768) and does not save/restore window size or position. This is a user experience issue as preferences are lost on restart.\n\n**File:** SmartFilesApp.java (lines 22-23)\n\n**Fix:** Save window bounds to settings on close, restore on startup.","status":"open","priority":4,"issue_type":"feature","created_at":"2026-01-15T22:37:37.871391+01:00","created_by":"abedurftig","updated_at":"2026-01-15T22:37:37.871391+01:00"} {"id":"smartfiles-yes","title":"Theme change event handler inconsistent with threading pattern","description":"handleLightThemeActivatedSettingsChangedEvent (lines 57-59) modifies the model directly without Platform.runLater(). While the theme toggle is likely called from the UI thread, the pattern should be consistent for safety.\n\n**Fix:** Wrap in Platform.runLater() for consistency with other handlers.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-15T22:36:52.962703+01:00","created_by":"abedurftig","updated_at":"2026-01-16T20:58:09.473652+01:00","closed_at":"2026-01-16T20:58:09.473652+01:00","close_reason":"Closed"} {"id":"smartfiles-yo2","title":"APP_EXECUTOR thread pool never shutdown on exit","description":"ApplicationViewBuilder.APP_EXECUTOR is a static CachedThreadPool that is never shut down when the application exits. This can prevent clean JVM shutdown and leak threads.\n\n**File:** ApplicationViewBuilder.java (line 39)\n\n**Fix:** Register a shutdown hook or use Spring's @PreDestroy to call APP_EXECUTOR.shutdown() on application exit.","status":"open","priority":2,"issue_type":"bug","created_at":"2026-01-15T22:35:36.312096+01:00","created_by":"abedurftig","updated_at":"2026-01-15T22:35:36.312096+01:00"} diff --git a/src/main/java/dev/arne/smartfiles/app/ApplicationModel.java b/src/main/java/dev/arne/smartfiles/app/ApplicationModel.java index 4469915..d6192ea 100644 --- a/src/main/java/dev/arne/smartfiles/app/ApplicationModel.java +++ b/src/main/java/dev/arne/smartfiles/app/ApplicationModel.java @@ -11,6 +11,7 @@ import lombok.Getter; import lombok.Setter; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Set; import java.util.UUID; @@ -19,7 +20,11 @@ @Setter public class ApplicationModel { + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MMM d, yyyy 'at' HH:mm"); + private final StringProperty selectedDocumentNameProperty = new SimpleStringProperty(null); + private final StringProperty documentDateCreatedProperty = new SimpleStringProperty(""); + private final StringProperty documentDateLastModifiedProperty = new SimpleStringProperty(""); private final BooleanProperty lightModeActivated = new SimpleBooleanProperty(false); private final ObservableList documentsList = FXCollections.observableArrayList(); private final FilteredList filteredDocuments = new FilteredList<>(documentsList, _ -> true); @@ -80,12 +85,15 @@ public void setSelectedDocument(ArchiveEntry selectedDocument) { selectedDocumentProperty.setValue(selectedDocument); selectedDocumentNameProperty.setValue(selectedDocument.getName()); descriptionProperty.setValue(selectedDocument.getSummary()); + documentDateCreatedProperty.setValue(selectedDocument.getDateCreated().format(DATE_FORMATTER)); + documentDateLastModifiedProperty.setValue(selectedDocument.getDateLastModified().format(DATE_FORMATTER)); updateDocumentTags(); } public void updateDocumentTags() { tagsProperty.removeIf(_ -> true); tagsProperty.addAll(selectedDocumentProperty.get().getTags()); + refreshDocumentDateLastModified(); } public void updateDescription(String description) { @@ -94,6 +102,14 @@ public void updateDescription(String description) { if (selectedDocument != null) { selectedDocument.setSummary(description); refreshDocumentInList(selectedDocument); + refreshDocumentDateLastModified(); + } + } + + private void refreshDocumentDateLastModified() { + var selectedDocument = selectedDocumentProperty.get(); + if (selectedDocument != null) { + documentDateLastModifiedProperty.setValue(selectedDocument.getDateLastModified().format(DATE_FORMATTER)); } } @@ -136,6 +152,8 @@ public void clearSelectedDocument() { selectedDocumentProperty.setValue(null); selectedDocumentNameProperty.setValue(null); descriptionProperty.setValue(""); + documentDateCreatedProperty.setValue(""); + documentDateLastModifiedProperty.setValue(""); tagsProperty.clear(); } } diff --git a/src/main/java/dev/arne/smartfiles/app/ApplicationViewBuilder.java b/src/main/java/dev/arne/smartfiles/app/ApplicationViewBuilder.java index 3d07090..416f971 100644 --- a/src/main/java/dev/arne/smartfiles/app/ApplicationViewBuilder.java +++ b/src/main/java/dev/arne/smartfiles/app/ApplicationViewBuilder.java @@ -255,6 +255,16 @@ private VBox createRightBottom() { vBox.getChildren().add(createAreaLabel("Document details")); + var dateCreatedLabel = new Label(); + dateCreatedLabel.textProperty().bind(model.getDocumentDateCreatedProperty().map(d -> d.isEmpty() ? "" : "Created: " + d)); + dateCreatedLabel.getStyleClass().add("sf-footer-label"); + vBox.getChildren().add(dateCreatedLabel); + + var dateModifiedLabel = new Label(); + dateModifiedLabel.textProperty().bind(model.getDocumentDateLastModifiedProperty().map(d -> d.isEmpty() ? "" : "Modified: " + d)); + dateModifiedLabel.getStyleClass().add("sf-footer-label"); + vBox.getChildren().add(dateModifiedLabel); + descriptionField = new TextField(); descriptionField.setPromptText("Click to add description"); descriptionField.textProperty().bindBidirectional(model.getDescriptionProperty()); diff --git a/src/main/java/dev/arne/smartfiles/core/service/ArchiveServiceImpl.java b/src/main/java/dev/arne/smartfiles/core/service/ArchiveServiceImpl.java index 69f3531..5dac031 100644 --- a/src/main/java/dev/arne/smartfiles/core/service/ArchiveServiceImpl.java +++ b/src/main/java/dev/arne/smartfiles/core/service/ArchiveServiceImpl.java @@ -87,6 +87,7 @@ public void addTag(UUID selectedDocumentId, String text) { var entry = archive.getArchiveEntries().get(selectedDocumentId); var newTag = new Tag(text); entry.getTags().add(newTag); + entry.updateLastModified(); publisher.publishEvent(new DocumentTagAddedEvent(newTag, selectedDocumentId)); publisher.publishEvent(new AllTagsUpdatedEvent(getAllUniqueTags())); saveArchiveAndPublishUpdate(); diff --git a/src/test/java/dev/arne/smartfiles/app/ApplicationModelTest.java b/src/test/java/dev/arne/smartfiles/app/ApplicationModelTest.java index 4eedee3..3ea5410 100644 --- a/src/test/java/dev/arne/smartfiles/app/ApplicationModelTest.java +++ b/src/test/java/dev/arne/smartfiles/app/ApplicationModelTest.java @@ -230,9 +230,24 @@ void clearSelectedDocument_clearsAllSelectionProperties() { assertNull(model.getSelectedDocumentProperty().get()); assertNull(model.getSelectedDocumentNameProperty().get()); assertEquals("", model.getDescriptionProperty().get()); + assertEquals("", model.getDocumentDateCreatedProperty().get()); + assertEquals("", model.getDocumentDateLastModifiedProperty().get()); assertTrue(model.getTagsProperty().isEmpty()); } + @Test + void setSelectedDocument_setsDateProperties() { + var entry = createTestEntry("test.pdf", "Description"); + entry.setDateCreated(LocalDateTime.of(2024, 6, 15, 14, 30)); + entry.setDateLastModified(LocalDateTime.of(2024, 12, 25, 9, 0)); + model.getDocumentsList().add(entry); + + model.setSelectedDocument(entry); + + assertEquals("Jun 15, 2024 at 14:30", model.getDocumentDateCreatedProperty().get()); + assertEquals("Dec 25, 2024 at 09:00", model.getDocumentDateLastModifiedProperty().get()); + } + private ArchiveEntry createTestEntry(String name, String summary) { var entry = new ArchiveEntry(); entry.setId(UUID.randomUUID());