Skip to content

Add copy entire page as markdown feature to documentation - #100

Closed
hotlong with Copilot wants to merge 5 commits into
mainfrom
copilot/add-copy-code-to-markdown
Closed

hotlong with Copilot wants to merge 5 commits into
mainfrom
copilot/add-copy-code-to-markdown

Conversation

Copilot AI commented Jan 24, 2026 •

Copy link
Copy Markdown
Contributor

Implements functionality to copy entire documentation pages as raw Markdown content, addressing user request to copy complete articles rather than just individual code blocks.

Changes Made

New Component

  • apps/docs/components/copy-page-markdown.tsx: Client-side component that handles copying page content to clipboard
    • Uses Navigator Clipboard API for copy functionality
    • Provides visual feedback with state transition: "Copy as Markdown" → "Copied!" (resets after 2 seconds)
    • Styled with Tailwind CSS using consistent design tokens from the documentation theme
    • Uses lucide-react icons (Copy/Check) for visual indicators

Updated Component

  • apps/docs/app/[lang]/docs/[[...slug]]/page.tsx: Server-side page component
    • Reads raw MDX file content directly from filesystem at build/render time
    • Attempts multiple file path patterns to locate the correct MDX file (handles different language versions and directory structures)
    • Passes raw markdown content to the CopyPageMarkdown component as a prop
    • Integrated button into page header layout using flexbox, positioned next to the page title
    • Button only renders when markdown content is successfully loaded

Feature Description

Users can now copy the entire raw Markdown content of any documentation page by clicking the "Copy as Markdown" button located in the top-right corner of each page, adjacent to the page title. This allows users to easily export documentation content for offline use, editing, or sharing.

Button Placement

┌───────────────────────────────────────────────────┐
│  Client SDK          [📋 Copy as Markdown]        │
│  The official TypeScript client for ObjectStack  │
│───────────────────────────────────────────────────│
│  ## Features                                       │
│  - Auto-Discovery                                  │
│  ...                                               │
└───────────────────────────────────────────────────┘

Technical Approach

  1. Server-side reads the raw MDX file content from the filesystem using Node.js fs module
  2. Multiple file path patterns are checked to handle different documentation structures and language variants
  3. Content is passed as a prop to the client-side component
  4. Client component uses the Clipboard API to copy content when button is clicked
  5. Visual feedback is provided through React state management and conditional rendering

Known Pre-existing Issue

⚠️ The documentation site currently has a widespread lucide-react module resolution error that prevents pages from loading in development mode. This issue is not caused by this PR - it is a pre-existing configuration problem where MDX files directly import lucide-react but the server-side rendering context cannot properly resolve the module during compilation.

Error: Module not found: Can't resolve 'lucide-react' appears in multiple files including:

  • content/docs/concepts/*.mdx
  • content/docs/specifications/**/*.mdx
  • content/docs/index.mdx

This configuration issue needs to be resolved separately for the documentation site to function properly. Once fixed, the "Copy as Markdown" feature will work as intended.

Original prompt

文档查看页应支持复制码当markdown


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel

vercel Bot commented Jan 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
spec Error Error Jan 24, 2026 11:30am

Request Review

Copilot AI and others added 2 commits January 24, 2026 10:32
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for copying code as markdown in documentation view Verify code copy functionality in documentation Jan 24, 2026
Copilot AI requested a review from hotlong January 24, 2026 10:36
@hotlong

hotlong commented Jan 24, 2026

Copy link
Copy Markdown
Contributor

@copilot 我的意思是我想把整篇文章复制出来,而不是把其中某一个code,我知道原来那个就有

Copilot AI commented Jan 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot 我的意思是我想把整篇文章复制出来,而不是把其中某一个code,我知道原来那个就有

明白了!您想要复制整篇文章内容为 Markdown 格式,而不只是代码块。我现在会添加一个"复制整页为 Markdown"的功能。

@xuyushun441-sys

Copy link
Copy Markdown
Collaborator

@copilot 继续

Copilot AI commented Jan 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot 继续

好的,我继续完成"复制整页为 Markdown"的功能实现。

Copilot AI and others added 2 commits January 24, 2026 11:27
Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
huangyiirene pushed a commit that referenced this pull request Sep 17, 2026
…it cites

The comment justifying the posture gate on the legacy-grant deprecation notice
stated the PRE-reversal rule as current fact — "the `single` promotion is
pinned NEVER to consult that variable" — and cited
bootstrap-platform-admin-walled-owner.test.ts as live support for it. The
maintainer ruling of 2026-09-08 (decision batch #100, recorded on #16682)
reversed exactly that point: under `single` posture the first-boot promotion
consults OS_PLATFORM_OWNER_EMAIL first. The cited string survives in that test
only inside its re-authored block's account of what the case USED to assert, so
a reader following the citation landed on text contradicting the comment that
sent them there.

Re-keyed to the post-ruling behaviour, with the citation re-pointed at what the
pin records now, and the neighbouring "whichever way Choice 4B is eventually
ruled" clause corrected: 4B is ruled and filed, not landed. The gate's actual
justification is restated from what the reversal did NOT change — the
declared-owner leg mints the same unscoped admin_full_access row, and a rig
already holding one answers already_have_admin before that leg runs — so the
migration notice stays scoped to walled rigs.

Comment-only: no behaviour, no assertion, no logic touched.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CqmCgU5RGDoJYhHUMVp2af
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…it cites (objectstack-ai#18645)

Fixes objectstack-ai#18380

Clause-②: no

Comment-only repair at
`packages/core/src/security/resolve-authz-context.ts` §6b-config. No
behaviour, no assertion, no logic touched — the diff is 32 insertions /
11 deletions, all of them inside one `//` block.

## The defect

The comment justifying the posture gate on the legacy-grant deprecation
notice stated the PRE-reversal rule as current fact, and cited a pin as
live support for it:

> the `single` promotion is pinned NEVER to consult that variable
(`bootstrap-platform-admin-walled-owner.test.ts`, "never consults the
owner-email variable")

The maintainer ruling of 2026-09-08 (decision batch objectstack-ai#100, recorded on
objectstack-ai#16682, comment `5587754690`) reversed exactly that point, verbatim:

> **F3 — the Choice 4A sentence is superseded for this one point.**
Under `single` posture the first-boot promotion consults
`OS_PLATFORM_OWNER_EMAIL` first.

## What the pin records now — read, not assumed

| probe | reading |
|---|---|
| `never consults the owner-email variable`, repo-wide under `packages/`
| **1** —
`packages/plugins/plugin-security/src/bootstrap-platform-admin-walled-owner.test.ts:436`
|
| firing control `owner-email variable`, same corpus | **4 files** ⇒ the
1 above is a reading, not a dead grep |

That single hit lives at `:434-456`, inside a block that opens `⚠️
RE-AUTHORED by objectstack-ai#16682`, says the case "used to assert the opposite",
quotes the maintainer ruling, and is followed by a case titled `a
declared owner DOES redirect the single-org promotion (objectstack-ai#16682), and
'single' still promotes`. So the citation **resolved** and did **not**
support the claim — it recorded its reversal. The repaired comment now
cites the pin for what the pin says.

## What the reversal did NOT change — the gate's real justification

Verified by reading
`packages/plugins/plugin-security/src/bootstrap-platform-admin.ts`
(read-only; another lane):

- The declared-owner leg mints the **same** unscoped `admin_full_access`
row — `promote()` is one call site serving both legs (`:867`),
`organization_id: null`.
- A rig that already holds such a row never reaches that leg: `if
(!walled && unscopedHolder) return { reason: 'already_have_admin' }`
(`:687`) runs first.
- The ruling's own second sentence preserves the standing this gate
rests on: "The rest of Choice 4A (objectstack-ai#11974, 2026-08-25) stands: retiring
the walled write must not retire the `single` one, and the over-denial
invariant (`adminPromoted === true` with a grant row minted) stays
pinned."

⇒ The notice's first half ("it is removed in a later release") is still
false for a `single` rig, so the posture gate stays right. Its second
half ("Re-anchor ... by declaring its administrators in configuration")
is **no longer inert** under `single` — which is what the old comment
got backwards — but it still does not move such a rig off the grant row.
The repaired comment says that, instead of the reversed premise.

## The neighbouring clause was stale too, and is fixed

The old text read "That holds whichever way Choice 4B (objectstack-ai#11979) is
eventually ruled". Checked rather than assumed: **4B is already ruled**,
and what is pending is the landing — objectstack-ai#11663 comment `5404675670`
(maintainer acceptance 2026-08-25), verbatim: "**4B is ruled as the
sequenced follow-up, not dropped** (card filed ...)"; objectstack-ai#11979 is open and
`pm:blocked` behind objectstack-ai#11978. The clause now says ruled and filed, not
landed.

## Verification — final head `cb602a03b3`

Gate derivation and reconciliation were run once, on this head:

```
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RANFILE
  -> 47 derived, 45 run, 2 NOT-MEASURED, 0 UNRUN   (exit 0)
```

- **44 of 45 green.**
- `pnpm check:cross-package-test-inputs` :: **exit 1** — the known gate
defect **objectstack-ai#18621**, not this PR's red. It fires on the mere existence of
`packages/spec/dist/`, which the dependency-closure build creates.
Control measured here: with `packages/spec/dist/` moved aside the same
command exits **0** ("OK: 29 package(s) read outside themselves, all
declared"), and restored byte-for-byte afterwards (216 files before, 216
after).
- `pnpm check:dual-build-cjs-loads` and `pnpm check:lean-entry-closure`
:: **exit 3, PREREQUISITE NOT MET** — both refuse to measure without a
whole-repo `dist/`. Recorded as NOT MEASURED, declared to CI; a
comment-only diff cannot move either.

Targeted, under the shared verify lock:

```
pnpm --filter '@objectstack/core...' build      -> VERDICT command-exit 0
pnpm --filter @objectstack/core typecheck       -> exit 0 (tsc + examples + check:test-typecheck)
pnpm --filter @objectstack/core test            -> exit 0 — Test Files 51 passed (51), Tests 1316 passed (1316)
```

Repo-wide `pnpm lint` (`eslint . --no-inline-config`) also run in full:
**exit 0**, so no narrowing had to be declared.

## Changeset decision: none, `skip-changeset` — measured, not assumed

`@objectstack/core` ships `files: ["dist", "README.md",
"CHANGELOG.md"]`. After building the package, those paths were grepped
with a positive control:

| probe | hits in the published paths |
|---|---|
| needle — text from this PR's comment ("the notice's FIRST half is
false", "re-authored block") | **0** |
| second needle — a pre-existing body comment from the same function
("This gates the NOTICE and nothing else") | **0** |
| **positive control** — a docblock on the exported
`hasPlatformAdminStanding` ("the ID-SHAPED platform-admin question") |
**1** in `dist/index.d.ts`, **1** in `dist/index.d.cts` |

The control lands, so the instrument is live; the needles do not. A
function-**body** comment reaches no published artifact: the declaration
emitter carries only declaration-level docblocks, esbuild drops body
comments from `dist/*.js` / `*.cjs`, and `dropSourcesContent` keeps the
source text out of `dist/*.map`. Nothing published moves ⇒ no changeset,
and the `skip-changeset` label is applied.

## Acceptance notes

- **noted, not filed** —
`bootstrap-platform-admin-walled-owner.test.ts`'s enclosing `describe`
title still reads `single posture — "first user is owner" is ruled
reasonable and UNCHANGED (Choice 4A)`, while the case inside it now
asserts that a declared owner redirects the promotion. The block's own
re-authored docblock explains the split correctly, so this is a
title-level nit, not a defect, and it is in the `domain:services` lane
(read-only for this card). Who will touch it: objectstack-ai#11979's implementer —
Choice 4B rewrites exactly this block when it lands.
- No other stale claim about the `single` promotion exists in the edited
file: `OS_PLATFORM_OWNER_EMAIL` appears once more, in the
`hasPlatformAdminStanding` docblock, and that text is accurate.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01CqmCgU5RGDoJYhHUMVp2af)_

Co-authored-by: Claude <noreply@anthropic.com>

This branch had an error being deployed

1 failed deployment
Preview — cebc20b4 Deployed Jan 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants