@@ -2194,9 +2194,36 @@ function toggleAnnotationPanel(): void {
21942194 setAnnotationPanelOpen ( annotationPanelUserPref ) ;
21952195}
21962196
2197- /** Total count of annotations + filled form fields for the sidebar badge. */
2197+ /**
2198+ * Derived state of a form field relative to the PDF baseline.
2199+ * Not stored — computed on demand by comparing formFieldValues to
2200+ * pdfBaselineFormValues.
2201+ */
2202+ type FieldState =
2203+ | "unchanged" // current === baseline (came from the PDF, untouched)
2204+ | "modified" // baseline exists but current differs
2205+ | "cleared" // baseline exists but current is absent/empty
2206+ | "added" ; // no baseline — user-filled or fill_form
2207+
2208+ function fieldState ( name : string ) : FieldState {
2209+ const cur = formFieldValues . get ( name ) ;
2210+ const base = pdfBaselineFormValues . get ( name ) ;
2211+ if ( base === undefined ) return "added" ;
2212+ if ( cur === undefined || cur === "" || cur === false ) return "cleared" ;
2213+ return cur === base ? "unchanged" : "modified" ;
2214+ }
2215+
2216+ /** All field names that should appear in the panel: current ∪ baseline.
2217+ * Cleared baseline fields remain visible (crossed out) so they can be
2218+ * reverted individually. */
2219+ function panelFieldNames ( ) : Set < string > {
2220+ return new Set ( [ ...formFieldValues . keys ( ) , ...pdfBaselineFormValues . keys ( ) ] ) ;
2221+ }
2222+
2223+ /** Total count of annotations + form fields for the sidebar badge.
2224+ * Uses the union so cleared baseline items still contribute. */
21982225function sidebarItemCount ( ) : number {
2199- return annotationMap . size + formFieldValues . size ;
2226+ return annotationMap . size + panelFieldNames ( ) . size ;
22002227}
22012228
22022229function updateAnnotationsBadge ( ) : void {
@@ -2327,12 +2354,19 @@ function renderAnnotationPanel(): void {
23272354 byPage . get ( page ) ! . push ( tracked ) ;
23282355 }
23292356
2330- // Group form fields by page
2331- const fieldsByPage = new Map < number , [ string , string | boolean ] [ ] > ( ) ;
2332- for ( const [ name , value ] of formFieldValues ) {
2357+ // Group form fields by page — iterate the UNION so cleared baseline
2358+ // fields remain visible (crossed out) with a per-item revert button.
2359+ const fieldsByPage = new Map < number , string [ ] > ( ) ;
2360+ for ( const name of panelFieldNames ( ) ) {
23332361 const page = fieldNameToPage . get ( name ) ?? 1 ;
23342362 if ( ! fieldsByPage . has ( page ) ) fieldsByPage . set ( page , [ ] ) ;
2335- fieldsByPage . get ( page ) ! . push ( [ name , value ] ) ;
2363+ fieldsByPage . get ( page ) ! . push ( name ) ;
2364+ }
2365+ // Sort fields by their intrinsic document order within each page
2366+ for ( const names of fieldsByPage . values ( ) ) {
2367+ names . sort (
2368+ ( a , b ) => ( fieldNameToOrder . get ( a ) ?? 0 ) - ( fieldNameToOrder . get ( b ) ?? 0 ) ,
2369+ ) ;
23362370 }
23372371
23382372 // Collect all pages that have annotations or form fields
@@ -2369,8 +2403,8 @@ function renderAnnotationPanel(): void {
23692403 pageNum === currentPage ,
23702404 ( body ) => {
23712405 // Form fields first
2372- for ( const [ name , value ] of fields ) {
2373- body . appendChild ( createFormFieldCard ( name , value ) ) ;
2406+ for ( const name of fields ) {
2407+ body . appendChild ( createFormFieldCard ( name ) ) ;
23742408 }
23752409 // Then annotations
23762410 for ( const tracked of annotations ) {
@@ -2541,54 +2575,85 @@ function createAnnotationCard(tracked: TrackedAnnotation): HTMLElement {
25412575 return card ;
25422576}
25432577
2544- function createFormFieldCard (
2545- name : string ,
2546- value : string | boolean ,
2547- ) : HTMLElement {
2578+ const TRASH_SVG = `<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 3h8M4.5 3V2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v1M5 5.5v3M7 5.5v3M3 3l.5 7a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1L9 3"/></svg>` ;
2579+ const REVERT_SVG = `<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 6a4 4 0 1 1 1.2 2.85"/><path d="M2 9V6h3"/></svg>` ;
2580+
2581+ /** Revert one field to its PDF-stored baseline value. */
2582+ function revertFieldToBaseline ( name : string ) : void {
2583+ const base = pdfBaselineFormValues . get ( name ) ;
2584+ if ( base === undefined ) return ;
2585+ formFieldValues . set ( name , base ) ;
2586+ // Remove our storage override → widget falls back to PDF's /V = baseline
2587+ if ( pdfDocument ) {
2588+ const ids = fieldNameToIds . get ( name ) ;
2589+ if ( ids ) for ( const id of ids ) pdfDocument . annotationStorage . remove ( id ) ;
2590+ }
2591+ }
2592+
2593+ function createFormFieldCard ( name : string ) : HTMLElement {
2594+ const state = fieldState ( name ) ;
2595+ const value = formFieldValues . get ( name ) ;
2596+ const baseValue = pdfBaselineFormValues . get ( name ) ;
2597+
25482598 const card = document . createElement ( "div" ) ;
25492599 card . className = "annotation-card" ;
2600+ if ( state === "cleared" ) card . classList . add ( "annotation-card-cleared" ) ;
25502601
25512602 const row = document . createElement ( "div" ) ;
25522603 row . className = "annotation-card-row" ;
25532604
2554- // Color swatch ( blue for form fields)
2605+ // Swatch: solid blue normally; crossed-out for cleared baseline fields
25552606 const swatch = document . createElement ( "div" ) ;
25562607 swatch . className = "annotation-card-swatch" ;
2557- swatch . style . background = "#4a90d9" ;
2608+ if ( state === "cleared" ) {
2609+ swatch . classList . add ( "annotation-card-swatch-cleared" ) ;
2610+ swatch . innerHTML = `<svg width="10" height="10" viewBox="0 0 10 10" stroke="#4a90d9" stroke-width="1.5" stroke-linecap="round"><path d="M2 2l6 6M8 2L2 8"/></svg>` ;
2611+ } else {
2612+ swatch . style . background = "#4a90d9" ;
2613+ }
2614+ // Subtle modified marker
2615+ if ( state === "modified" ) swatch . title = "Modified from file" ;
25582616 row . appendChild ( swatch ) ;
25592617
25602618 // Field label
2561- const label = getFormFieldLabel ( name ) ;
25622619 const nameEl = document . createElement ( "span" ) ;
25632620 nameEl . className = "annotation-card-type" ;
2564- nameEl . textContent = label ;
2621+ nameEl . textContent = getFormFieldLabel ( name ) ;
25652622 row . appendChild ( nameEl ) ;
25662623
2567- // Field value preview
2624+ // Value preview: show current, or struck-out baseline when cleared
2625+ const shown = state === "cleared" ? baseValue : value ;
25682626 const displayValue =
2569- typeof value === "boolean" ? ( value ? "checked" : "unchecked" ) : value ;
2627+ typeof shown === "boolean" ? ( shown ? "checked" : "unchecked" ) : shown ;
25702628 if ( displayValue ) {
25712629 const valueEl = document . createElement ( "span" ) ;
25722630 valueEl . className = "annotation-card-preview" ;
25732631 valueEl . textContent = displayValue ;
25742632 row . appendChild ( valueEl ) ;
25752633 }
25762634
2577- // Delete button
2578- const deleteBtn = document . createElement ( "button" ) ;
2579- deleteBtn . className = "annotation-card-delete" ;
2580- deleteBtn . title = "Clear field" ;
2581- deleteBtn . innerHTML = `<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 3h8M4.5 3V2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v1M5 5.5v3M7 5.5v3M3 3l.5 7a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1L9 3"/></svg>` ;
2582- deleteBtn . addEventListener ( "click" , ( e ) => {
2635+ // Action button: revert for modified/cleared baseline fields, trash otherwise
2636+ const isRevertable = state === "modified" || state === "cleared" ;
2637+ const actionBtn = document . createElement ( "button" ) ;
2638+ actionBtn . className = "annotation-card-delete" ;
2639+ actionBtn . title = isRevertable
2640+ ? "Revert to value stored in file"
2641+ : "Clear field" ;
2642+ actionBtn . innerHTML = isRevertable ? REVERT_SVG : TRASH_SVG ;
2643+ actionBtn . addEventListener ( "click" , ( e ) => {
25832644 e . stopPropagation ( ) ;
2584- formFieldValues . delete ( name ) ;
2585- clearFieldInStorage ( name ) ;
2645+ if ( isRevertable ) {
2646+ revertFieldToBaseline ( name ) ;
2647+ } else {
2648+ formFieldValues . delete ( name ) ;
2649+ clearFieldInStorage ( name ) ;
2650+ }
25862651 updateAnnotationsBadge ( ) ;
25872652 renderAnnotationPanel ( ) ;
25882653 renderPage ( ) ;
25892654 persistAnnotations ( ) ;
25902655 } ) ;
2591- row . appendChild ( deleteBtn ) ;
2656+ row . appendChild ( actionBtn ) ;
25922657
25932658 // Click handler: navigate to page and focus form input
25942659 card . addEventListener ( "click" , ( ) => {
@@ -2853,28 +2918,31 @@ function clearFieldInStorage(name: string): void {
28532918 for ( const id of ids ) storage . setValue ( id , { value : clearValue } ) ;
28542919}
28552920
2856- /** Remove all user-sourced entries from annotationStorage, leaving the
2857- * PDF's own stored values intact. */
2858- function clearUserFormStorage ( ) : void {
2859- if ( ! pdfDocument ) return ;
2860- for ( const [ name ] of formFieldValues ) {
2861- if ( pdfBaselineFormValues . has ( name ) ) continue ; // PDF-native — leave alone
2862- const ids = fieldNameToIds . get ( name ) ;
2863- if ( ids ) for ( const id of ids ) pdfDocument . annotationStorage . remove ( id ) ;
2864- }
2865- }
2866-
28672921/**
28682922 * Revert to what's in the PDF file: restore baseline annotations, restore
28692923 * baseline form values, discard all user edits. Result: diff is empty, clean.
2924+ *
2925+ * Form fields: remove ALL storage overrides — every field reverts to the
2926+ * PDF's /V (which IS baseline). We can't skip baseline-named fields: if the
2927+ * user edited one, our override is in storage under that name, and skipping
2928+ * it leaves the widget showing the stale edit.
28702929 */
28712930function resetToBaseline ( ) : void {
28722931 clearAnnotationMap ( ) ;
28732932 for ( const def of pdfBaselineAnnotations ) {
28742933 annotationMap . set ( def . id , { def : { ...def } , elements : [ ] } ) ;
28752934 }
28762935
2877- clearUserFormStorage ( ) ;
2936+ if ( pdfDocument ) {
2937+ const storage = pdfDocument . annotationStorage ;
2938+ for ( const name of new Set ( [
2939+ ...formFieldValues . keys ( ) ,
2940+ ...pdfBaselineFormValues . keys ( ) ,
2941+ ] ) ) {
2942+ const ids = fieldNameToIds . get ( name ) ;
2943+ if ( ids ) for ( const id of ids ) storage . remove ( id ) ;
2944+ }
2945+ }
28782946 formFieldValues . clear ( ) ;
28792947 for ( const [ name , value ] of pdfBaselineFormValues ) {
28802948 formFieldValues . set ( name , value ) ;
0 commit comments