diff --git a/frontend/src/features/chunks/ui/StaleStoresStrip.vue b/frontend/src/features/chunks/ui/StaleStoresStrip.vue
new file mode 100644
index 0000000..34bc886
--- /dev/null
+++ b/frontend/src/features/chunks/ui/StaleStoresStrip.vue
@@ -0,0 +1,143 @@
+
+
+
+
+ {{ link.store }}
+
+
+ {{ t('chunks.stale.pushedAt', { date: formatRelativeTime(link.pushedAt, locale) }) }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/DocChunksTab.vue b/frontend/src/pages/DocChunksTab.vue
index ba44fa5..443ca06 100644
--- a/frontend/src/pages/DocChunksTab.vue
+++ b/frontend/src/pages/DocChunksTab.vue
@@ -25,6 +25,11 @@
+
import { ref, onMounted } from 'vue'
-import type { DocTreeNode } from '../shared/types'
+import type { DocTreeNode, DocStoreLink } from '../shared/types'
import { fetchDocumentTree } from '../features/document/api'
import DocTreeRail from '../features/document/ui/DocTreeRail.vue'
import ChunksEditor from '../features/chunks/ui/ChunksEditor.vue'
+import StaleStoresStrip from '../features/chunks/ui/StaleStoresStrip.vue'
import { useI18n } from '../shared/i18n'
const props = defineProps<{
docId: string
availableStores: string[]
+ storeLinks?: DocStoreLink[]
}>()
const { t } = useI18n()
diff --git a/frontend/src/pages/DocWorkspacePage.vue b/frontend/src/pages/DocWorkspacePage.vue
index 9951a59..85b67a8 100644
--- a/frontend/src/pages/DocWorkspacePage.vue
+++ b/frontend/src/pages/DocWorkspacePage.vue
@@ -41,6 +41,7 @@
v-if="activeMode === 'chunks'"
:doc-id="id"
:available-stores="doc.stores ?? []"
+ :store-links="doc.storeLinks"
/>
diff --git a/frontend/src/shared/i18n.ts b/frontend/src/shared/i18n.ts
index 52aa0a1..103e777 100644
--- a/frontend/src/shared/i18n.ts
+++ b/frontend/src/shared/i18n.ts
@@ -46,9 +46,9 @@ const messages: Messages = {
'status.Failed': 'Échoué',
'status.tooltip.Uploaded': 'Fichier reçu, en attente de parsing.',
'status.tooltip.Parsed': 'Document parsé avec succès.',
- 'status.tooltip.Chunked': 'Chunks générés, prêts à être poussés vers un store.',
- 'status.tooltip.Ingested': 'Indexé dans au moins un store.',
- 'status.tooltip.Stale': 'Chunks modifiés depuis le dernier push — re-ingestion requise.',
+ 'status.tooltip.Chunked': 'Chunks générés, prêts à être ingérés dans un store.',
+ 'status.tooltip.Ingested': 'Ingéré dans au moins un store.',
+ 'status.tooltip.Stale': 'Chunks modifiés depuis la dernière ingestion — ré-ingestion requise.',
'status.tooltip.Failed': 'Une étape du pipeline a échoué. Réessayez.',
// Document library (#211, #212, #213)
@@ -66,14 +66,14 @@ const messages: Messages = {
'docs.filterClear': 'Effacer les filtres',
'docs.selected': '{n} sélectionné(s)',
'docs.bulkRechunk': 'Re-chunker',
- 'docs.bulkPush': 'Pousser vers un store…',
+ 'docs.bulkPush': 'Ingérer dans un store…',
'docs.bulkDelete': 'Supprimer',
'docs.bulkCancel': 'Annuler la sélection',
'docs.deleteConfirm': 'Supprimer {n} document(s) ? Cette action est irréversible.',
- 'docs.pushTitle': 'Pousser vers un store',
+ 'docs.pushTitle': 'Ingérer dans un store',
'docs.pushLabel': 'Store cible',
'docs.pushPlaceholder': 'Nom du store…',
- 'docs.pushSubmit': 'Pousser',
+ 'docs.pushSubmit': 'Ingérer',
'docs.pushCancel': 'Annuler',
'docs.jobDispatched': 'Job lancé ({jobId})',
@@ -316,33 +316,33 @@ const messages: Messages = {
'Le chunking n\u2019est pas disponible pour cette analyse. Les documents volumineux trait\u00e9s par batch ne g\u00e9n\u00e8rent pas la structure interne n\u00e9cessaire au d\u00e9coupage. Coming soon !',
// Search (legacy nav label moved to the top with the other legacy keys)
- 'search.hint': 'Saisissez un terme pour rechercher dans les chunks indexés.',
+ 'search.hint': 'Saisissez un terme pour rechercher dans les chunks ingérés.',
// Ingestion / My Documents
'ingestion.ingest': 'Ingérer',
'ingestion.document': 'Document',
'ingestion.chunkCount': 'Chunks prêts',
- 'ingestion.successMessage': 'Indexation terminée avec succès !',
+ 'ingestion.successMessage': 'Ingestion terminée avec succès !',
'ingestion.ingesting': 'Ingestion...',
- 'ingestion.reindex': 'Ré-indexer',
- 'ingestion.indexed': 'Indexé',
- 'ingestion.notIndexed': 'Non indexé',
- 'ingestion.chunksIndexed': '{n} chunks indexés',
+ 'ingestion.reindex': 'Ré-ingérer',
+ 'ingestion.indexed': 'Ingéré',
+ 'ingestion.notIndexed': 'Non ingéré',
+ 'ingestion.chunksIndexed': '{n} chunks ingérés',
'ingestion.openInStudio': 'Ouvrir dans le Studio',
- 'ingestion.deleteIndex': "Supprimer de l'index",
+ 'ingestion.deleteIndex': 'Retirer du store',
'ingestion.deleteConfirm':
'Retirer ce document de l\u2019index ? Les chunks seront supprimés mais le document source restera.',
'ingestion.unavailable': 'Ingestion non disponible',
'ingestion.filterAll': 'Tous',
- 'ingestion.filterIndexed': 'Indexés',
- 'ingestion.filterNotIndexed': 'Non indexés',
+ 'ingestion.filterIndexed': 'Ingérés',
+ 'ingestion.filterNotIndexed': 'Non ingérés',
'ingestion.sortName': 'Nom',
'ingestion.sortDate': 'Date',
'ingestion.search': 'Rechercher...',
'ingestion.searchChunks': 'Rechercher dans les chunks…',
'ingestion.noResults': 'Aucun résultat pour « {q} ».',
'ingestion.stepEmbedding': 'Embedding…',
- 'ingestion.stepIndexing': 'Indexation…',
+ 'ingestion.stepIndexing': 'Ingestion…',
'ingestion.stepDone': 'Terminé',
'ingestion.opensearchConnected': 'OpenSearch connecté',
'ingestion.opensearchDisconnected': 'OpenSearch déconnecté',
@@ -396,13 +396,19 @@ const messages: Messages = {
'chunks.diffAdded': 'ajout\u00e9(s)',
'chunks.diffModified': 'modifi\u00e9(s)',
'chunks.diffRemoved': 'supprim\u00e9(s)',
- 'chunks.pushTitle': 'Pousser vers un store',
+ 'chunks.pushTitle': 'Ing\u00e9rer dans un store',
'chunks.pushStore': 'Store cible',
'chunks.pushPlaceholder': 'Nom du store\u2026',
'chunks.pushSummary': '{embeds} chunks \u00b7 ~{tokens} tokens',
- 'chunks.pushConfirm': 'Pousser',
+ 'chunks.pushConfirm': 'Ing\u00e9rer',
'chunks.pushCancel': 'Annuler',
'chunks.pushedJob': 'Job lanc\u00e9 : {jobId}',
+ // Stale stores strip (#224)
+ 'chunks.stale.pushedAt': 'ing\u00e9r\u00e9 le {date}',
+ 'chunks.stale.reingest': 'R\u00e9-ing\u00e9rer',
+ 'chunks.stale.reingestAll': 'R\u00e9-ing\u00e9rer tous les obsol\u00e8tes',
+ 'chunks.stale.jobDispatched': 'Job lanc\u00e9 : {jobId}',
+
'chunks.selected': '{n} s\u00e9lectionn\u00e9(s)',
'chunks.bulkDrop': 'Supprimer la s\u00e9lection',
'chunks.bulkMerge': 'Fusionner la s\u00e9lection',
@@ -450,9 +456,9 @@ const messages: Messages = {
'status.Failed': 'Failed',
'status.tooltip.Uploaded': 'File received, awaiting parsing.',
'status.tooltip.Parsed': 'Document parsed successfully.',
- 'status.tooltip.Chunked': 'Chunks generated, ready to push to a store.',
- 'status.tooltip.Ingested': 'Indexed in at least one store.',
- 'status.tooltip.Stale': 'Chunks modified since last push — re-ingestion required.',
+ 'status.tooltip.Chunked': 'Chunks generated, ready to ingest into a store.',
+ 'status.tooltip.Ingested': 'Ingested into at least one store.',
+ 'status.tooltip.Stale': 'Chunks modified since last ingestion — re-ingestion required.',
'status.tooltip.Failed': 'A pipeline step failed. Retry to recover.',
// Document library (#211, #212, #213)
@@ -470,14 +476,14 @@ const messages: Messages = {
'docs.filterClear': 'Clear filters',
'docs.selected': '{n} selected',
'docs.bulkRechunk': 'Re-chunk',
- 'docs.bulkPush': 'Push to store…',
+ 'docs.bulkPush': 'Ingest into store…',
'docs.bulkDelete': 'Delete',
'docs.bulkCancel': 'Cancel selection',
'docs.deleteConfirm': 'Delete {n} document(s)? This action cannot be undone.',
- 'docs.pushTitle': 'Push to store',
+ 'docs.pushTitle': 'Ingest into store',
'docs.pushLabel': 'Target store',
'docs.pushPlaceholder': 'Store name…',
- 'docs.pushSubmit': 'Push',
+ 'docs.pushSubmit': 'Ingest',
'docs.pushCancel': 'Cancel',
'docs.jobDispatched': 'Job dispatched ({jobId})',
@@ -710,32 +716,32 @@ const messages: Messages = {
'chunking.batchNotice':
'Chunking is not available for this analysis. Large documents processed in batch mode do not generate the internal structure required for chunking. Coming soon!',
- 'search.hint': 'Enter a term to search through indexed chunks.',
+ 'search.hint': 'Enter a term to search through ingested chunks.',
'ingestion.ingest': 'Ingest',
'ingestion.document': 'Document',
'ingestion.chunkCount': 'Chunks ready',
- 'ingestion.successMessage': 'Indexing completed successfully!',
+ 'ingestion.successMessage': 'Ingestion completed successfully!',
'ingestion.ingesting': 'Ingesting...',
- 'ingestion.reindex': 'Re-index',
- 'ingestion.indexed': 'Indexed',
- 'ingestion.notIndexed': 'Not indexed',
- 'ingestion.chunksIndexed': '{n} chunks indexed',
+ 'ingestion.reindex': 'Re-ingest',
+ 'ingestion.indexed': 'Ingested',
+ 'ingestion.notIndexed': 'Not ingested',
+ 'ingestion.chunksIndexed': '{n} chunks ingested',
'ingestion.openInStudio': 'Open in Studio',
- 'ingestion.deleteIndex': 'Remove from index',
+ 'ingestion.deleteIndex': 'Remove from store',
'ingestion.deleteConfirm':
'Remove this document from the index? Chunks will be deleted but the source document will remain.',
'ingestion.unavailable': 'Ingestion unavailable',
'ingestion.filterAll': 'All',
- 'ingestion.filterIndexed': 'Indexed',
- 'ingestion.filterNotIndexed': 'Not indexed',
+ 'ingestion.filterIndexed': 'Ingested',
+ 'ingestion.filterNotIndexed': 'Not ingested',
'ingestion.sortName': 'Name',
'ingestion.sortDate': 'Date',
'ingestion.search': 'Search...',
'ingestion.searchChunks': 'Search indexed chunks…',
'ingestion.noResults': 'No results for "{q}".',
'ingestion.stepEmbedding': 'Embedding…',
- 'ingestion.stepIndexing': 'Indexing…',
+ 'ingestion.stepIndexing': 'Ingesting…',
'ingestion.stepDone': 'Done',
'ingestion.opensearchConnected': 'OpenSearch connected',
'ingestion.opensearchDisconnected': 'OpenSearch unreachable',
@@ -786,13 +792,19 @@ const messages: Messages = {
'chunks.diffAdded': 'added',
'chunks.diffModified': 'modified',
'chunks.diffRemoved': 'removed',
- 'chunks.pushTitle': 'Push to store',
+ 'chunks.pushTitle': 'Ingest into store',
'chunks.pushStore': 'Target store',
'chunks.pushPlaceholder': 'Store name\u2026',
'chunks.pushSummary': '{embeds} chunks \u00b7 ~{tokens} tokens',
- 'chunks.pushConfirm': 'Push',
+ 'chunks.pushConfirm': 'Ingest',
'chunks.pushCancel': 'Cancel',
'chunks.pushedJob': 'Job dispatched: {jobId}',
+ // Stale stores strip (#224)
+ 'chunks.stale.pushedAt': 'ingested on {date}',
+ 'chunks.stale.reingest': 'Re-ingest',
+ 'chunks.stale.reingestAll': 'Re-ingest all stale',
+ 'chunks.stale.jobDispatched': 'Job dispatched: {jobId}',
+
'chunks.selected': '{n} selected',
'chunks.bulkDrop': 'Drop selected',
'chunks.bulkMerge': 'Merge selected',
diff --git a/frontend/src/shared/types.ts b/frontend/src/shared/types.ts
index bd8fdd5..065472d 100644
--- a/frontend/src/shared/types.ts
+++ b/frontend/src/shared/types.ts
@@ -17,6 +17,13 @@ export type DocumentLifecycleState =
| 'Stale'
| 'Failed'
+/** Per-store ingestion state for a document (#224). */
+export interface DocStoreLink {
+ store: string
+ state: DocumentLifecycleState
+ pushedAt: string | null
+}
+
export interface Document {
id: string
filename: string
@@ -30,6 +37,8 @@ export interface Document {
lifecycleStateAt: string | null
/** Stores this document has been pushed to (added in E1 #203). */
stores?: string[]
+ /** Per-store state detail (#224). Present when backend returns storeLinks. */
+ storeLinks?: DocStoreLink[]
}
export interface PipelineOptions {