Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 41 additions & 1 deletion docs/product/periodic-reports.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,22 @@ This is presentation validation, not a claim that a URL is reachable, public or
authorized for another audience. Source collection, frozen evidence, redaction
and delivery authorization retain those responsibilities.

Text reports include the same authored overview values, labels and details as
the HTML artifact. When that overview is present, it replaces the generated
title-only opening; reports without highlights retain their compiled summary.
This exposes existing editorial content, not an independent verification of it.

Opaque source references receive stable numbers in document order. Repeated
references share one exact, redacted entry in **Source references**, including
sources used only by supporting items. Web addresses keep their existing links.
Markdown retains the index in its full linear body. HTML opens its existing
appendix when a source number is followed and moves keyboard focus to the
reference; reopening the fragment restores it. Printing includes the full index
and restores the prior disclosure state afterward. No lookup, upload or new
source authority is introduced.

An already frozen report retains its original bytes and generation identity.
Validation can reconstruct the exact previous source-link presentation without
Validation can reconstruct the exact previous inline-reference and source-link presentations without
rewriting its artifacts, invoking generation or acknowledging publication.
The full frozen source, authored response, bundle, candidate and body must still
match; an unsupported or altered presentation remains unavailable. New reports
Expand Down Expand Up @@ -60,6 +74,32 @@ are 390×844 viewport emulations.
| --- | --- |
| ![HTML retains the same source references and substantive report](../assets/personal-workspace/steward-reports/citations/html-desktop.png) | ![The standalone HTML report at a narrow viewport](../assets/personal-workspace/steward-reports/citations/html-narrow.png) |

The following same-input synthetic report contains ten substantive items and
two repeated internal source identities. The text overview now retains its
authored numbers and source gaps; nine inline digests become two indexed
references. The text changes from 2,875 to 2,420 characters while preserving
every item title, summary, detail and next action. This is a fixture result,
not a token-cost or live-report quality measurement. The expanded HTML index
also stays within a 390-pixel viewport; its exact references were read back
from the browser's printed PDF. The same frozen pre-change Markdown/HTML
artifacts remain byte-identical under read-only reconstruction.

| Text before | Text after |
| --- | --- |
| ![Repeated digests occupy the text report](../assets/personal-workspace/steward-reports/provenance/before-text-desktop.png) | ![Authored overview and compact source numbers](../assets/personal-workspace/steward-reports/provenance/after-text-desktop.png) |

| Narrow text before | Narrow text after |
| --- | --- |
| ![Previous text at 390 pixels](../assets/personal-workspace/steward-reports/provenance/before-text-narrow.png) | ![Overview and complete outcomes at 390 pixels](../assets/personal-workspace/steward-reports/provenance/after-text-narrow.png) |

| HTML before | HTML after |
| --- | --- |
| ![Inline source identities in the standalone report](../assets/personal-workspace/steward-reports/provenance/before-html-desktop.png) | ![Sources remain one action from the report body](../assets/personal-workspace/steward-reports/provenance/after-html-desktop.png) |

| Narrow HTML | Expanded source index | Narrow expanded source index |
| --- | --- | --- |
| ![The report body at 390 pixels](../assets/personal-workspace/steward-reports/provenance/after-html-narrow.png) | ![Complete references in the existing appendix](../assets/personal-workspace/steward-reports/provenance/source-expanded-desktop.png) | ![Full source identities wrap inside the narrow report](../assets/personal-workspace/steward-reports/provenance/source-expanded-narrow.png) |

The same local calendar can explicitly repeat an already completed native Task
in its original managed Session. This backend/configuration slice is described
below; arbitrary new tasks from natural language and the unified Agent UI
Expand Down
6 changes: 5 additions & 1 deletion loopx/capabilities/periodic_report/private_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,12 @@ def validate_private_generation(folder: Path) -> dict[str, Any]:
render_periodic_report_markdown(bundle["document"]), render_periodic_report_html(bundle["document"])])
if checked != bundle:
# Rendering can improve after a window was frozen. Reconstruct only the
# exact prior source-link presentation; never rewrite or regenerate it.
# exact prior presentation; never rewrite or regenerate it.
# Both candidates still require full bundle, source and body equality.
checked = build_periodic_report_generation_bundle(document=bundle["document"], artifacts=[
render_periodic_report_markdown(bundle["document"], _legacy_layout=True),
render_periodic_report_html(bundle["document"], _legacy_layout=True)])
if checked != bundle:
checked = build_periodic_report_generation_bundle(document=bundle["document"], artifacts=[
render_periodic_report_markdown(bundle["document"], _legacy_source_links=True),
render_periodic_report_html(bundle["document"], _legacy_source_links=True)])
Expand Down
79 changes: 73 additions & 6 deletions loopx/presentation/renderers/periodic_report_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
)
from ..public_safety import redact_public_text
from .periodic_report_markdown import (
_source_references,
periodic_report_labels,
periodic_report_source_url,
render_periodic_report_markdown,
Expand Down Expand Up @@ -204,6 +205,7 @@ def _render_item(
*,
index: int,
labels: Mapping[str, str],
references: Mapping[str, int],
_legacy_source_links: bool = False,
) -> str:
title = _safe_text(item.get("title"), maximum=240)
Expand Down Expand Up @@ -242,6 +244,12 @@ def _render_item(
'target="_blank" rel="noopener noreferrer">'
f'{labels["source_link"]}</a>'
)
elif isinstance(source_ref, str) and source_ref in references:
number = references[source_ref]
source = (
f'<a class="source-ref" href="#report-source-{number}" data-report-source-link>'
f'{labels["source"]} {number}</a>'
)
elif source_ref:
source = (
f'<span class="source-ref">{_safe_text(source_ref, maximum=500)}</span>'
Expand Down Expand Up @@ -269,13 +277,14 @@ def _render_section(
section_index: int,
item_start: int,
labels: Mapping[str, str],
references: Mapping[str, int],
_legacy_source_links: bool = False,
) -> tuple[str, int]:
section_id = _safe_attr(section.get("section_id"), maximum=128)
title = _safe_text(section.get("title"), maximum=160)
items = _primary_items(section)
rows = "".join(
_render_item(item, index=item_start + offset, labels=labels,
_render_item(item, index=item_start + offset, labels=labels, references=references,
_legacy_source_links=_legacy_source_links)
for offset, item in enumerate(items)
)
Expand All @@ -297,6 +306,7 @@ def _render_supporting_sections(
sections: Sequence[Mapping[str, Any]],
*,
labels: Mapping[str, str],
references: Mapping[str, int],
_legacy_source_links: bool = False,
) -> str:
groups = []
Expand All @@ -321,6 +331,13 @@ def _render_supporting_sections(
if source_url
else ""
)
reference = item.get("source_ref")
if isinstance(reference, str) and reference in references:
number = references[reference]
source = (
f'<a href="#report-source-{number}" data-report-source-link>'
f'{labels["source"]} {number}</a>'
)
notes.append(
'<article class="supporting-note">'
f'<p class="supporting-label">{label}</p>'
Expand Down Expand Up @@ -373,6 +390,48 @@ def _render_source_receipts(
)


def _render_source_references(references: Mapping[str, int], labels: Mapping[str, str]) -> str:
if not references:
return ""
rows = "".join(
f'<li id="report-source-{number}" data-report-source tabindex="-1">'
f'<code>{_safe_text(reference, maximum=500)}</code></li>'
for reference, number in references.items()
)
return f'<section class="source-index"><h3>{labels["source_references"]}</h3><ol>{rows}</ol></section>'


# Kept separate from the prior bytes for exact frozen-artifact reconstruction.
_SOURCE_CSS = """
.layout > main, .layout > .rail { min-width: 0; }
.source-index { margin-bottom: 18px; }
.source-index h3 { margin: 0 0 10px; font: 600 13px/1.4 var(--sans); }
.source-index ol { padding-left: 24px; }
.source-index li { padding: 5px 0; font-size: 12px; }
.source-index code { overflow-wrap: anywhere; font: 11px/1.6 var(--mono); }
@media print { .supporting { display: block !important; } }
"""

_SOURCE_SCRIPT = """
(() => {
const appendix = document.querySelector('[data-supporting-context]');
const reveal = (hash) => {
const target = document.getElementById(hash.slice(1));
if (!target || !target.hasAttribute('data-report-source')) return;
appendix.open = true;
requestAnimationFrame(() => { target.scrollIntoView({block: 'center'}); target.focus({preventScroll: true}); });
};
document.querySelectorAll('[data-report-source-link]').forEach((link) =>
link.addEventListener('click', () => reveal(link.hash)));
window.addEventListener('hashchange', () => reveal(location.hash));
reveal(location.hash);
let wasOpen;
window.addEventListener('beforeprint', () => { if (wasOpen === undefined) wasOpen = appendix.open; appendix.open = true; });
window.addEventListener('afterprint', () => { if (wasOpen !== undefined) appendix.open = wasOpen; wasOpen = undefined; });
})();
"""


_CSS = """
:root {
color-scheme: light;
Expand Down Expand Up @@ -603,12 +662,15 @@ def _render_source_receipts(
"""


def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_links: bool = False) -> dict[str, Any]:
def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_links: bool = False,
_legacy_layout: bool = False) -> dict[str, Any]:
"""Render one dense, self-contained report without changing its semantics."""

if document.get("schema_version") != DOCUMENT_SCHEMA:
raise ValueError(f"document must use {DOCUMENT_SCHEMA}")
validate_periodic_report_editorial(document)
_legacy_layout = _legacy_layout or _legacy_source_links
references = {} if _legacy_layout else _source_references(document)
sections = _items(document.get("sections"))
primary_sections = [
section
Expand All @@ -625,7 +687,8 @@ def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_l
item_count = sum(len(_primary_items(section)) for section in sections)
supporting_item_count = sum(len(_supporting_items(section)) for section in sections)
document_digest = _document_digest(document)
markdown_artifact = render_periodic_report_markdown(document, _legacy_source_links=_legacy_source_links)
markdown_artifact = render_periodic_report_markdown(document, _legacy_source_links=_legacy_source_links,
_legacy_layout=_legacy_layout)

filters = [
'<a class="section-filter" href="#report-top" data-section-filter="all" '
Expand All @@ -646,6 +709,7 @@ def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_l
section_index=section_index,
item_start=next_item_index,
labels=labels,
references=references,
_legacy_source_links=_legacy_source_links,
)
rendered_sections.append(section_html)
Expand All @@ -660,6 +724,8 @@ def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_l
summary = _safe_text(editorial.get("summary"), maximum=600)
period_label = _safe_text(editorial.get("period_label"), maximum=160)
highlights = _items(editorial.get("highlights"))
if highlights and not _legacy_layout:
summary = ""
if highlights:
facts = []
for highlight in highlights[:4]:
Expand All @@ -682,7 +748,7 @@ def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_l
f'<div class="fact"><strong>{len(sources)}</strong><span>{labels["sources"]}</span></div>'
"</div>"
)
supporting_sections = _render_supporting_sections(sections, labels=labels,
supporting_sections = _render_supporting_sections(sections, labels=labels, references=references,
_legacy_source_links=_legacy_source_links)
content = (
"<!doctype html>\n"
Expand All @@ -691,7 +757,7 @@ def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_l
'<meta http-equiv="Content-Security-Policy" '
"content=\"default-src 'none'; style-src 'unsafe-inline'; "
"script-src 'unsafe-inline'; img-src data:\">"
f"<title>{title}</title><style>{_CSS}</style></head><body>"
f"<title>{title}</title><style>{_CSS}{_SOURCE_CSS if references else ''}</style></head><body>"
'<div class="shell" id="report-top" data-renderer="html_artifact_v0" '
'data-presentation="editorial_dense_v2">'
'<header class="masthead"><div class="mast-grid"><div>'
Expand All @@ -712,6 +778,7 @@ def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_l
'<details class="supporting" data-supporting-context><summary>'
f'{labels["supporting_context"]}</summary><div class="supporting-body">'
f"{supporting_sections}"
f"{_render_source_references(references, labels)}"
'<dl class="metadata">'
f'<dt>{labels["profile"]}</dt><dd>{profile_id} · {profile_version}</dd>'
f'<dt>{labels["generated"]}</dt><dd>{generated_at}</dd>'
Expand All @@ -725,7 +792,7 @@ def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_l
f'data-copy-failed="{_safe_attr(labels["copy_failed"], maximum=80)}"></div>'
'<script type="application/json" data-report-markdown>'
f"{_embedded_json(markdown_artifact['content'])}</script>"
f"<script>{_SCRIPT}</script></body></html>\n"
f"<script>{_SCRIPT}{_SOURCE_SCRIPT if references else ''}</script></body></html>\n"
)
content_digest = hashlib.sha256(content.encode("utf-8")).hexdigest()
return {
Expand Down
41 changes: 37 additions & 4 deletions loopx/presentation/renderers/periodic_report_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ def _markdown_source(value: object, labels: Mapping[str, str], *, _legacy_source
return f"{labels['source']}{labels['label_separator']}{fence}{padding}{source}{padding}{fence}"


def _source_references(document: Mapping[str, Any]) -> dict[str, int]:
"""Number opaque references once, in document order, for both formats."""
references: dict[str, int] = {}
for section in _items(document.get("sections")):
for item in _items(section.get("items")):
reference = item.get("source_ref")
if isinstance(reference, str) and reference and not periodic_report_source_url(reference):
references.setdefault(reference, len(references) + 1)
return references


def periodic_report_labels(language: object) -> dict[str, str]:
"""Return the small built-in report UI vocabulary for one language."""

Expand All @@ -82,6 +93,7 @@ def periodic_report_labels(language: object) -> dict[str, str]:
"no_items": "暂无条目。",
"next": "下一步",
"source": "来源",
"source_references": "来源标识",
"supporting_appendix": "附录:投递与运行信息",
"label_separator": ":",
}
Expand All @@ -90,6 +102,7 @@ def periodic_report_labels(language: object) -> dict[str, str]:
"no_items": "No items.",
"next": "Next",
"source": "source",
"source_references": "Source references",
"supporting_appendix": "Appendix: delivery and runtime context",
"label_separator": ": ",
}
Expand All @@ -100,6 +113,7 @@ def _render_markdown_item(
item: Mapping[str, Any],
*,
labels: Mapping[str, str],
references: Mapping[str, int],
_legacy_source_links: bool = False,
) -> None:
item_title = _safe(item.get("title"), maximum=240)
Expand All @@ -109,7 +123,10 @@ def _render_markdown_item(
if status:
headline += f" · `{status}`"
if source_ref:
headline += f" · {_markdown_source(source_ref, labels, _legacy_source_links=_legacy_source_links)}"
if isinstance(source_ref, str) and source_ref in references:
headline += f" · {labels['source']} {references[source_ref]}"
else:
headline += f" · {_markdown_source(source_ref, labels, _legacy_source_links=_legacy_source_links)}"
lines.append(headline)
lines.append(f" - {_safe(item.get('summary'), maximum=1000)}")
for detail in _items(item.get("details")):
Expand All @@ -129,12 +146,16 @@ def render_periodic_report_markdown(
document: Mapping[str, Any],
*,
_legacy_source_links: bool = False,
_legacy_layout: bool = False,
) -> dict[str, Any]:
"""Render normalized sections without changing ordering or business meaning."""

if document.get("schema_version") != DOCUMENT_SCHEMA:
raise ValueError(f"document must use {DOCUMENT_SCHEMA}")
validate_periodic_report_editorial(document)
# Read-only reconstruction of artifacts frozen before these presentation fixes.
_legacy_layout = _legacy_layout or _legacy_source_links
references = {} if _legacy_layout else _source_references(document)
title = _safe(document.get("title"), maximum=200)
window = _mapping(document.get("period_window"))
editorial = _mapping(document.get("editorial"))
Expand All @@ -150,7 +171,15 @@ def render_periodic_report_markdown(
),
]
summary = _safe(editorial.get("summary"), maximum=600)
if summary:
highlights = [] if _legacy_layout else _items(editorial.get("highlights"))
if highlights:
lines.append("")
for highlight in highlights:
label = _safe(highlight.get("label"), maximum=80)
value = _safe(highlight.get("value"), maximum=48)
detail = _safe(highlight.get("detail"), maximum=160)
lines.append(f"- **{label}{labels['label_separator']}{value}**" + (f" · {detail}" if detail else ""))
elif summary:
lines.extend(["", summary])
primary_item_count = 0
supporting_item_count = 0
Expand All @@ -172,13 +201,17 @@ def render_periodic_report_markdown(
lines.append(f"- {labels['no_items']}")
continue
for item in section_items:
_render_markdown_item(lines, item, labels=labels, _legacy_source_links=_legacy_source_links)
_render_markdown_item(lines, item, labels=labels, references=references, _legacy_source_links=_legacy_source_links)
if supporting_sections:
lines.extend(["", f"## {labels['supporting_appendix']}"])
for section, section_items in supporting_sections:
lines.extend(["", f"### {_safe(section.get('title'), maximum=160)}", ""])
for item in section_items:
_render_markdown_item(lines, item, labels=labels, _legacy_source_links=_legacy_source_links)
_render_markdown_item(lines, item, labels=labels, references=references, _legacy_source_links=_legacy_source_links)
if references:
lines.extend(["", f"## {labels['source_references']}", ""])
for reference, number in references.items():
lines.append(f"{number}. {_markdown_source(reference, labels)}")
content = "\n".join(lines).rstrip() + "\n"
content_digest = hashlib.sha256(content.encode("utf-8")).hexdigest()
document_digest = hashlib.sha256(
Expand Down
Loading
Loading