entry https://tailscale.com/changelog/#2026-05-29-service
local /rss-offline/article-04009026a7f5cca2.html
text_lengths {'orig': 112122, 'isolated': 339, 'picked': 339}
Traceback (most recent call last):
  File "<stdin>", line 24, in <module>
  File "/home/sebas/work/projects/rss-offline/build.py", line 407, in make_pdf_printable_copy
    printable_path.write_text(html)
    ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/usr/lib/python3.13/pathlib/_local.py", line 557, in write_text
    return PathBase.write_text(self, data, encoding, errors, newline)
           ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/pathlib/_abc.py", line 651, in write_text
    with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:
         ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/pathlib/_local.py", line 539, in open
    return io.open(self, mode, buffering, encoding, errors, newline)
           ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PermissionError: [Errno 13] Permission denied: '/var/www/rss-offline/pdf/article-04009026a7f5cca2.print.html'

Command exited with code 1


bash

python - <<'PY'
import json, importlib.util, tempfile, subprocess, shutil
from pathlib import Path
from bs4 import BeautifulSoup

spec=importlib.util.spec_from_file_location('rssbuild','/home/sebas/work/projects/rss-offline/build.py')
mod=importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)

entries=json.loads(Path('/var/www/rss-offline/entries.json').read_text())
entry=next(e for e in entries if e['link']=='https://tailscale.com/changelog/#2026-05-29-service')
orig_html=(Path('/var/www/rss-offline')/Path(entry['local_path']).name).read_text()
print('text_lengths', {
  'orig': len(BeautifulSoup(orig_html,'html.parser').get_text(' ',strip=True)),
  'isolated': len(mod.isolate_anchor_section(BeautifulSoup(orig_html,'html.parser'), entry).get_text(' ',strip=True)),
})

tmpdir=Path(tempfile.mkdtemp(prefix='rss-offline-tailtest-', dir='/tmp'))
try:
    mod.OUTPUT_DIR = tmpdir
    shutil.copy2(Path('/var/www/rss-offline')/Path(entry['local_path']).name, tmpdir/Path(entry['local_path']).name)
    printable=mod.make_pdf_printable_copy(entry)
    txt=len(BeautifulSoup(printable.read_text(),'html.parser').get_text(' ',strip=True))
    print('printable_text', txt)
    pdf_path=tmpdir/'tailscale-test.pdf'
    mod.print_pdf(printable, pdf_path)
    info=subprocess.run(['pdfinfo', str(pdf_path)], capture_output=True, text=True, check=True).stdout
    for line in info.splitlines():
        if line.startswith(('Pages:','File size:')):
            print(line)
finally:
    shutil.rmtree(tmpdir, ignore_errors=True)
PY


text_lengths {'orig': 112122, 'isolated': 339}
printable_text 339
Pages:           1
File size:       34283 bytes


Updated #6 (pending → completed)

Enable native visited styling for PDF links in rss-offline

Inspect rss-offline app/site and adjust PDF links so browser native :visited state marks them as read when opened from https://ballbox-first.emperor-ratio.ts.net/rss-offline/.

Add direct PDF download button and preserve rss-offline scroll position

Update rss-offline PDF archive so each item has a separate direct download button while main link remains readable/visited, and restore page scroll position after opening PDFs.

Tighten rss-offline PDF extraction for noisy blogs

Inspect recent PDF inputs, especially GitHub Blog and DigitalOcean, and improve builder targeting/cleanup so printable copies focus on article content with less extra chrome. Do not rebuild.

Audit all recent rss-offline feed domains for PDF targeting

Inspect current entries by domain, identify article-body selectors per host where useful, extend builder host-specific print root targeting, and clean any temporary artifacts. No rebuild unless needed for validation-only local parsing.

Rebuild rss-offline latest PDF after targeting improvements

Run rss-offline builder so the newest PDF is regenerated with the new host-specific targeting, then report the resulting latest PDF size.

Validate Tailscale-only PDF rebuild reduction

Regenerate a Tailscale changelog printable/PDF for the affected entry using current builder logic, compare whether content is now narrowly scoped, and clean temp artifacts.


Sí. Lo probé simple y da bien.

Test:
- entry:
  - `https://tailscale.com/changelog/#2026-05-29-service`

Resultado con lógica nueva:
- HTML original capturado:
  - `112122` chars de texto
- después de aislar el anchor:
  - `339`
- printable final:
  - `339`
- PDF generado en test:
  - `1 page`
  - `34283 bytes`

O sea:
- antes: agarraba la página enorme/histórica
- ahora: queda recortado a la entrada puntual

Y limpié lo temporal en `/tmp`.


/new


queued new Telegram Pi session: pi-telegram


Listo. Roté la sesión de Telegram Pi.