The work record requested only two posts supplied by their addresses. Their saved metadata remained in the result folder. Looking beyond storage success, I found missing position fields alongside a web-only label and a large sorting value. That made it necessary to inspect the assumptions between successfully saving a post and actually measuring its search position.
Read the requested scope before interpreting the result
The human request was to download the specified posts, rather than collect an entire set of search results. The historical summary records two requested, two saved and no failures. It describes a content-collection result, not a verified position in search.
I did not turn those posts' prose or images into a claimed maker experience. I inspected the result and metadata structure and the program's identity, merge and display functions. The public article includes none of the original titles, authors, addresses or images. Its subject is how the program expresses success across different kinds of work.
Item identity and observed position are different values
The original program prioritizes a publisher identifier and post number when connecting web and mobile address forms. Equal titles with different post numbers remain different items. It has a normalized-title fallback for cases without identifiers, which should not carry the same certainty as an explicit identifier. The new synthetic model narrows that contract by requiring identity instead of copying the fallback.
Connecting an item does not merge its web and mobile positions into one value. The original candidate structure keeps separate positions, search titles, addresses and source fragments for the two channels. Merging candidates reduces repeated items while retaining differences in observation. Identity and the conditions behind an observation should not collapse into a single key.
The default branch becomes visible when both positions are absent
Both direct-download metadata records had null web and mobile positions. Yet ranking_presence was WEB_ONLY and best_rank was ten to the ninth power. A consumer could misread that combination as a web-only detection or an extremely low measured position. The records do not establish either measurement.
The code I first reviewed handled both channels and mobile-only, then returned WEB_ONLY for the remaining case. It omitted the case with neither position, while best_rank supplied a large sorting value. My initial review recorded that mismatch and separated it in a synthetic example. A later, separate local development task repaired the native helpers and saved wording. The follow-up below describes that repair; it does not mean historical output files were rewritten.
Even “not found” requires evidence that a check occurred
An absent position does not by itself mean the item was not found within a checked range. The check may not have been performed, or the page may have been unavailable. Those cases differ from reading the intended window and not finding the item. The wording attached to None changes what an operator should do next.
The public example accepts explicit states for not checked, scoped absence, unavailable and observed. It rejects zero and sorting sentinels as measured positions. An unavailable observation can retain the attempted range without claiming that the whole range was read. This full four-state model is the synthetic implementation’s contract. The collector repair described later records absence neutrally; it does not install that detailed model wholesale.
Do not let one favorable number stand in for two observations
The original best_rank helper selects a smaller position to sort candidates. Presenting that sort key as a universal position or performance metric would change its meaning. Values from different devices and observation conditions need an explicit comparison policy. This article is not a measurement of current search visibility or a particular post's performance.
The synthetic module displays both sources and rejects mismatched run or query aliases. Different channel ranges remain visible. Matching aliases do not authenticate equal real-world time, location, personalization or methodology either. The convenience of gathering material about one item is distinct from establishing comparable observations.
Keep content-collection and measurement outcomes separate
A post can be saved without its search position being measured, and a position can be observed before the full post is saved. Automatically filling one side with a success-like default because the other side succeeded makes later totals difficult to interpret. Those distinctions need to survive when metadata moves to another tool.
I connect the question with the existing per-field evidence module. Support for one field must not become support for every field in a row. That principle is reusable, but the earlier module does not perform searches or automatically integrate with this observation-state model. They remain components whose input meanings and test scopes need to be understood before integration.
Use synthetic counterexamples rather than a new live search
The initial content task reviewed selected source and historical request/result metadata. At that stage I did not execute the collector, connect a browser, cookies or accounts, or redownload posts and images. Historical save success was not borrowed as a new product regression result. Old control-tower output-link issues were recorded without altering registration. The later local development checks imported and exercised the actual native module in a separate stage, distinguished below.
The public example provides five actual synthetic execution records and sixteen tests, repeated after ZIP extraction. Its bilingual record selectors are checked with DOM stubs. Those are not live search-page or ranking measurements. The reusable lesson is a precise account of what was checked and what remains unknown, rather than a method for manufacturing a favorable number.
In the repair, I did not invent a reason for the missing value
Once a missing rank is found, changing it to “not checked” can seem obvious. Yet two empty fields do not prove that a search was never attempted either. In the native collector, I changed the case with neither rank to UNRECORDED and used neutral wording meaning “no rank recorded” in the display and saved HTML. I kept the repair narrow: express the fact that is known rather than attach a plausible explanation to a state whose cause is unknown.
WEB_ONLY remains the state with a recorded web rank, MOBILE_ONLY with a recorded mobile rank, and BOTH with both. The actual recorded numbers remain intact. This change performs no new search and reconstructs no observation window. Its purpose is to stop the next operator from confusing a recorded position with the absence of a recorded position.
For a customer, this can look like a wording fix. For a connected tool, it is an output-contract change. A table or aggregation that assumes rank is always numeric must handle absence explicitly. A consumer that maps an unfamiliar UNRECORDED value back to web-only would recreate the misunderstanding. Changing the producer is not evidence that every downstream consumer is compatible.
The sorting substitute stays out of saved rank fields
I changed best_rank to return the smaller recorded position and to return None when neither exists. The saved metadata therefore contains JSON null, while CSV contains a blank field. An absent rank is not turned into position zero or an extremely large position. That distinction needs to survive when another program reads the output file.
Sorting still needs a defined result. Inside the merge sort key, an absent rank is placed after recorded ranks with a sorting-only substitute. That substitute never becomes best_rank or an exported position. The change retains the existing secondary ordering rules for measured values and ties.
The same question can arise in other reporting screens: showing an unknown balance as zero or an unchecked delivery date as today makes a screen look complete while changing its basis for a decision. This record does not claim those other workflows were implemented. What I verified here was the rank helper and the save paths that consume its value.
I checked the path from helper return values to saved files
In the separate development stage I imported the actual native module and ran fifteen offline tests. They distinguish no recorded ranks, web-only, mobile-only and both. Beyond the base display-row helper, I passed synthetic input through the actual methods writing ranking CSV and post JSON and HTML, then read the files. Returning None in a helper would not complete the repair if a later save stage filled it with a number again.
For records with measured positions, I also compared the earlier version. Across eighty merge input combinations generated with a fixed random seed, the result lists and order matched the prior implementation. That comparison includes publisher/post identity, channel positions and merged duplicates. It is regression evidence for the tested combinations, rather than a guarantee covering every possible search result.
These tests did not start a browser or execute a full collection run. Network connections, child-process execution and GUI construction were blocked, and I skipped the constructor that creates a live session. The actual save method received independently authored synthetic text without images. Consequently these results do not verify real site access, image downloads, login persistence, GUI rendering or current search positions.
I also corrected the test setup itself. Replacing the Tk class with a function to block GUI startup prevented the native GUI subclass from importing. That was a test-double error, rather than a newly discovered collector defect. I blocked the constructor instead and ran the tests without replaying the source edit. Distinguishing the failing layer helped avoid an unnecessary product change.
The repaired code and historical records retain different versions
I did not quietly rewrite the old downloaded results under the new rule. Three metadata files still matched the hashes retained in the earlier evidence. Those files record what the program wrote at the time. Overwriting them merely because the new output contract differs would also erase evidence explaining why the repair was necessary.
I backed up six existing source files from before the repair and six from after it, extracted all twelve into a separate local directory and compared their hashes. The restored modified module passed the same fifteen tests. Having an archive and verifying that restored code passes its checks are separate claims. This work neither redownloaded the historical posts nor restarted the operating program.
I sent a small, customer-free function slice and the actual verification scope to the shared code judgment tool. After the repair, a separate request reviewed the current code without receiving the initial judgment. Its assessment is supporting evidence, not a replacement for execution. I retained the file-level tests, prior-result comparisons and actual restore record alongside it.
The follow-up left at that stage concerned a whole-blog caller passing a response-list sequence number into a rank field. A later, separate development task repaired that calling path, as described below. It did not establish compatibility for every external file consumer or install the public four-state model wholesale. The two repairs and their verification boundaries belong to different stages.
The caller manufactured ranks after the output helpers were repaired
In the follow-up, the whole-blog wrapper passed its loop index into mobile_rank. That path reads a blog post list and saves the posts; it does not inspect search results. Because it supplied a positive integer to the save engine, the repaired UNRECORDED branch had no reason to run.
I exercised the actual main function and native post-save method with two synthetic posts. The first and second response-list items were saved in JSON and HTML as mobile positions 1 and 2, MOBILE_ONLY, and best_rank 1 and 2. Correct output rules cannot recover the meaning of a number supplied under the wrong field. That is why I extended the check from the repaired helpers to the entrance constructing their input.
List position now has its own field
Rather than discard list order, I retained it as list_position. The wrapper copies the received rows, numbers them from one and writes them to the list JSON. This number describes the item's place in this particular response. It is neither search visibility nor a position guaranteed to stay fixed across later fetches or runs.
The candidate now receives mobile_rank=None. Together with its already absent web rank, the unchanged save engine writes null for both search positions, UNRECORDED for presence and neutral “no rank recorded” wording in HTML. The genuine search GUI, automatic search runner and save engine's calculations and display rules were not edited in this repair.
Resume and partial failure make the distinction more useful. If an already saved item is skipped or another item fails to save, response-list position differs from the order of newly saved posts. Adding position to copied rows first retains the response position of skipped and failed items as well. I also tested that the caller's original row objects were not modified in memory.
The follow-up exercised the wrapper and save path together
Twelve new tests execute the main function taken from the real source together with the existing native save method. Synthetic substitutes provide list and post responses, while network connections, child processes and GUI startup are blocked. The tests read candidate ranks, saved JSON and HTML, original-document and text files, dated folders, and empty-list, failure and resume outcomes. They do not fetch new posts from the platform or download images.
I reran the fifteen existing search regressions and retained results across eighty measured-rank merge combinations. The twelve wrapper tests plus those fifteen checks therefore passed as twenty-seven tests. I archived the two changed files and the unchanged save-engine context as three files per source version. The extracted updated copy passed the same twenty-seven tests. This is a later execution, distinct from the earlier fifteen-test and twelve-file backup record described above.
The harness executes main's function code directly, so it does not run top-level import behavior that changes the working directory or loads dependencies. These results must not become a claim about an actual browser, login, current list API responses, image saving or GUI rendering. The verified meaning of the input through storage and the scope of a live full collection run need separate descriptions.
Preserving old records leaves a deliberate boundary
For the resume test I deliberately supplied synthetic legacy metadata containing an old rank. The wrapper skipped that item while preserving the file's bytes and modification time; only newly saved posts followed the new rule. I neither rewrote historical downloads in bulk nor fetched their originals again. Existing files can still contain the former contract.
Migrating historical files would require a separate copy and an evidence-based rule. A numeric rank alone cannot establish whether it came from list order or a real search observation. Before automatically clearing such values, inspect the original calling path and the records supporting it. This repair does not authenticate historical meanings or prove that the list API returned every post.
The shared module's not_performed state also requires an explicit declaration by a host that knows the reason. Evidence that this whole-blog path never calls search carries more information than the general UNRECORDED value alone. I did not implement a new adapter between them. The follow-up's central lesson is to inspect how a field was produced, rather than infer its meaning solely from whether it is empty.