feat: keep reviewed Filed history (Closes #882)
CI / lint (pull_request) Successful in 1m44s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 52s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-15 09:10:05 +00:00
1 parent 403190e888
commit a57aa879e2
7 files changed
+129 -22

No files matched your search

+4
View File
@@ -285,6 +285,10 @@ textarea { resize: vertical; min-height: 120px; }
.queue-finder button { padding-inline:14px; }
#search-older-work { width:100%; }
.my-work-list { display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); gap:10px; }
.filed-history-tabs { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; margin:10px 0; }
.filed-history-tabs[hidden] { display:none; }
.filed-history-tabs button { min-height:44px; min-width:0; }
.filed-history-tabs button[aria-pressed="true"] { border-color:#60a5fa; background:#17365d; }
.draft-card { display:flex; flex-direction:column; gap:8px; min-width:0; scroll-margin-bottom:calc(76px + env(safe-area-inset-bottom)); }
.draft-card:focus-visible { outline:3px solid #60a5fa; outline-offset:3px; border-color:#93c5fd; }
.draft-preview { color:var(--muted); overflow-wrap:anywhere; }
+18 -10
View File
@@ -61,7 +61,7 @@
let mobileQueueCounts = {};
function openFiledFollowUp() {
selectMobileQueue('filed');
const target = filedFollowUpTarget(lastMyWork);
const target = filedFollowUpTarget(completedFiledReview.visible(lastMyWork));
if (!target) {
qs('#my-work-action-status').textContent = 'No filed issues are ready to open.';
return 'empty';
@@ -156,6 +156,7 @@
const WORK_FILTER_KEY = 'stackchain.my-work-filter.v1';
const WORK_MILESTONE_KEY = 'stackchain.my-work-milestone.v1';
let selectedWorkFilter = 'all';
let selectedFiledView = 'needs-review';
let selectedWorkMilestone = 'all';
let queueFindQuery = '';
let savedWorkFilter = null;
@@ -220,6 +221,10 @@
storage: localStorage,
getLogin() { return planningOwnerLogin; },
});
const filedHistoryTabs = createFiledHistoryTabs({
root:qs('#filed-history-tabs'), review:completedFiledReview,
onSelect:view => { selectedFiledView = view; renderMyWork(); },
});
let completedFiledSyncFlight = null;
let activeMyWork = [];
let laterMyWork = [];
@@ -344,7 +349,7 @@
.then(snapshot => {
const changed = completedFiledReview.adopt(snapshot);
if (lastContextSnapshot && changed) {
lastMyWork = completedFiledReview.visible(buildMyWork(lastContextSnapshot));
lastMyWork = buildMyWork(lastContextSnapshot);
refreshMyWorkView();
}
if (pending.length) qs('#my-work-action-status').textContent =
@@ -2545,7 +2550,7 @@
}
function paintMyWork(data) {
lastMyWork = completedFiledReview.visible(buildMyWork(data));
lastMyWork = buildMyWork(data);
refreshMyWorkView();
void syncCompletedFiledReviews();
}
@@ -2566,7 +2571,8 @@
function refreshMyWorkView({ reconcileSession = true } = {}) {
lastDrafts = listDrafts();
const partitioned = laterWork.partition(lastMyWork, {
const actionableMyWork = filedHistoryTabs.prepare(lastMyWork);
const partitioned = laterWork.partition(actionableMyWork, {
pruneMissing: !Object.values(workPagination).some(page => page?.has_more),
});
activeMyWork = partitioned.active;
@@ -2574,7 +2580,7 @@
const authoritativeTodayReconciliation = liveMode && hasContextSnapshot &&
!lastContextSnapshot?.error &&
!Object.values(workPagination).some(page => page?.has_more);
todayMyWork = todayWork.reconcile(lastMyWork, {
todayMyWork = todayWork.reconcile(actionableMyWork, {
pruneMissing: authoritativeTodayReconciliation,
onPrune: retiredIds => {
const queued = retiredIds.map(id => todaySync.enqueue('remove', id)).every(Boolean);
@@ -2871,9 +2877,11 @@
renderDrafts();
qs('#bulk-mark-read-bar').hidden = true;
qs('#load-more-notifications').hidden = true;
qs('#filed-history-tabs').hidden = true;
return;
}
const queueItems = selectedWorkFilter === 'today' ?
const filedItems = filedHistoryTabs.render(selectedFiledView, selectedWorkFilter === 'filed');
const queueItems = selectedWorkFilter === 'filed' ? filedItems : selectedWorkFilter === 'today' ?
filterMyWork(todayMyWork, 'all', selectedWorkMilestone) : selectedWorkFilter === 'later' ?
filterMyWork(laterMyWork, 'all', selectedWorkMilestone) : selectedWorkFilter === 'agenda' ?
agendaMyWork(activeMyWork) :
@@ -2954,8 +2962,9 @@
(selectedWorkFilter === 'agenda' && workPagination.issue?.has_more ?
(agendaChecking ? 'Checking all assigned deadlines…' :
'Older assigned deadlines remain unchecked. Retry the Agenda check.') : (incomplete ?
'More work is available. Load the next page.' :
'No ' + (selectedWorkFilter === 'attention' ? 'items need attention' : (selectedWorkFilter === 'review' ? 'reviews' : (selectedWorkFilter === 'update' ? 'unread updates' : (selectedWorkFilter === 'later' ? 'deferred work' : (selectedWorkFilter === 'all' ? 'work' : selectedWorkFilter + ' items'))))) + '.')) + '</div>');
'More work is available. Load the next page.' : (selectedWorkFilter === 'filed' && selectedFiledView === 'reviewed' ?
'No reviewed outcomes yet' :
'No ' + (selectedWorkFilter === 'attention' ? 'items need attention' : (selectedWorkFilter === 'review' ? 'reviews' : (selectedWorkFilter === 'update' ? 'unread updates' : (selectedWorkFilter === 'later' ? 'deferred work' : (selectedWorkFilter === 'all' ? 'work' : selectedWorkFilter + ' items'))))) + '.'))) + '</div>');
cardPlanning.wire();
document.querySelectorAll('[data-select-notification-id]').forEach(input => {
input.addEventListener('change', () => {
@@ -3472,10 +3481,9 @@
return;
}
const acknowledged = selectedIssue;
lastMyWork = completedFiledReview.visible(lastMyWork);
closeIssueSheet(false);
refreshMyWorkView();
const target = filedFollowUpTarget(lastMyWork);
const target = filedFollowUpTarget(completedFiledReview.visible(lastMyWork));
qs('#my-work-action-status').textContent = 'Reviewed ' + acknowledged.key + '.' +
(target ? ' Opening the next Filed item. Acknowledgement sync pending.' :
' Filed review is complete. Acknowledgement sync pending.');
+4
View File
@@ -247,6 +247,10 @@
</div>
<button id="protect-today" type="button">Protect Today</button>
</section>
<div class="filed-history-tabs" id="filed-history-tabs" role="group" aria-label="Filed outcome view" hidden>
<button type="button" data-filed-view="needs-review" aria-pressed="true">Needs review <span id="filed-needs-review-count">0</span></button>
<button type="button" data-filed-view="reviewed" aria-pressed="false">Reviewed <span id="filed-reviewed-count">0</span></button>
</div>
<div class="my-work-list" id="my-work-list"></div>
<div class="small" id="work-page-status" aria-live="polite"></div>
<button class="load-more-work" id="load-more-work" type="button" hidden>Load older work</button>
+37 -4
View File
@@ -691,12 +691,22 @@ function createCompletedFiledReview({
repository:String(item?.repository || ''), number:item?.number, updated_at:stamp,
});
const save = value => storage?.setItem(ownerKey(), JSON.stringify({ version:1, ...value }));
const partition = items => {
const acknowledged = read().items;
const needsReview = [];
const reviewed = [];
(items || []).forEach(item => {
const isReviewed = item?.is_completed &&
acknowledged[identity(item)] === String(item.updated_at || '');
(isReviewed ? reviewed : needsReview).push(item);
});
reviewed.sort((left, right) => String(right.updated_at || '').localeCompare(String(left.updated_at || '')));
return { needsReview, reviewed };
};
return {
partition,
visible(items) {
const acknowledged = read().items;
return (items || []).filter(item =>
!item?.is_completed || acknowledged[identity(item)] !== String(item.updated_at || '')
);
return partition(items).needsReview;
},
pending() {
const state = read();
@@ -742,6 +752,28 @@ function createCompletedFiledReview({
};
}
function createFiledHistoryTabs({ root, review, onSelect }) {
const buttons = Array.from(root.querySelectorAll('[data-filed-view]'));
let partition = { needsReview:[], reviewed:[] };
buttons.forEach(button => button.addEventListener('click', () => onSelect(button.dataset.filedView)));
return {
prepare(items) {
partition = review.partition(items);
root.querySelector('#filed-needs-review-count').textContent =
partition.needsReview.filter(item => item.is_filed).length;
root.querySelector('#filed-reviewed-count').textContent = partition.reviewed.length;
return partition.needsReview;
},
render(selected, visible) {
root.hidden = !visible;
buttons.forEach(button =>
button.setAttribute('aria-pressed', String(button.dataset.filedView === selected))
);
return selected === 'reviewed' ? partition.reviewed : partition.needsReview;
},
};
}
function agendaMyWork(items, now = new Date()) {
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const today = localDay(start);
@@ -1058,6 +1090,7 @@ function countMyWork(items) {
if (typeof module !== 'undefined' && module.exports) {
buildMyWork.filterMyWork = filterMyWork;
buildMyWork.createCompletedFiledReview = createCompletedFiledReview;
buildMyWork.createFiledHistoryTabs = createFiledHistoryTabs;
buildMyWork.agendaMyWork = agendaMyWork;
buildMyWork.milestoneLanes = milestoneLanes;
buildMyWork.createWorkSession = createWorkSession;