Commit graph

57 commits

Author SHA1 Message Date
Claude
38161604f9 Viewer: query field and results table (ifcurl-2iv, ifcurl-4u3)
viewer-url.js: parseIfcUrl returns query field; buildIfcUrl gains optional
fifth query param, included in the QS when non-empty.

viewer.html: add 'query' text field (label + input) in toolbar row 2
after the visibility selector. Add #query-panel (fixed bottom-centre)
with a scrollable table showing GlobalId | value rows, a header
displaying the query path, and a close button.

viewer.js: populate queryInput from parsed URL on load; include query
in buildIfcUrl() wrapper; wire queryInput into Enter-key and focus/blur
listeners. Add applyQuery(srcUrl, queryPath) which calls GET /query,
populates the table, and shows the panel. Called from main() after
applySelector when both selector and query params are present.

viewer-url.test.js: add tests for parseIfcUrl query field, buildIfcUrl
with query, and buildIfcUrl omitting empty query.

https://claude.ai/code/session_01NBsytCd6L7UQPiKbcLn4kn
2026-05-09 11:09:55 +00:00
Claude
8d13757c4d Viewer: add clash visibility mode (ifcurl-982)
viewer.html: add 'clash' option to the visibility <select> dropdown.

viewer.js: add clash branch in applySelector for both the simple
type-selector path and the complex server-side GUID path. Selected
elements are coloured 0xdc3232 (red, matching render.py's _CLASH_COLOR);
non-selected elements remain at their normal material colours, consistent
with highlight mode behaviour.

https://claude.ai/code/session_01NBsytCd6L7UQPiKbcLn4kn
2026-05-09 09:03:11 +00:00
4b96ee3206 viewer: show status hint when clip mode is active
Displays "Double-click model surface to place clip plane" in the status
bar when clip mode is toggled on, clears it when toggled off.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 22:30:24 +01:00
82756b5ebb Merge pull request #1 from OpeningDesign/forgejo/local-setup-docker
forgejo: add Docker-based local setup guide and Dockerfile
2026-04-28 00:02:33 +01:00
950349941c forgejo: add @thatopen/components-front, fix click-to-identify and model overview
Bundle OBCF (components-front) alongside OBC so Highlighter is sourced from the
correct package.  Update click handler to use item.getGuid() — GlobalId is no
longer in the attributes map in fragments 3.4.x.  Fix populateMetaPanel crash
(ClassificationGroupData now wraps ids in a .map property, not a plain object).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 00:01:19 +01:00
889645c2b2 forgejo: fix missing View in 3D button on commit file view pages
On /src/commit/{hash}/file.ifc pages, breadcrumb anchors match the
a[href*="/src/commit/"] selector before the Permalink anchor, returning
a directory URL that parseCommitHref rejects (no file path). Fix by
trying window.location.pathname first — it parses correctly when already
on a commit file URL — and only falling back to a Permalink anchor search
for branch/tag file views. Add a regression test for directory-level
commit hrefs returning null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:31:43 +01:00
d60bd044dd forgejo: add JS unit tests, extract pure functions into viewer-util.js
Extract parseCommitHref, isSimpleTypeSelector, subtractIdMap, and
isCommitHash into a new viewer-util.js ES module so they can be tested
without a browser. Convert ifcurl.js from an IIFE to an ES module
importing from viewer-util.js; update footer.tmpl to type="module".

Add node:test suites for viewer-url.js (32 tests) and viewer-util.js
(28 tests). Add npm test script and document it in the build section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:24:58 +01:00
Ryan Schultz
d7dd4f1ea1 forgejo: add Docker-based local setup guide and Dockerfile
Add forgejo/SETUP_LOCAL.md documenting a self-contained local deployment
of Forgejo + ifcurl on Windows using Docker Desktop. Covers Nginx reverse
proxy for PR diff images, correct Forgejo v15 custom paths (/data/gitea),
libosmesa6 for headless rendering, and a git URL rewrite so the ifcurl
container can clone repos from localhost:3000 via the Docker-internal
forgejo hostname.

Add docker/Dockerfile for the ifcurl preview service.

Link SETUP_LOCAL.md from README.md and forgejo/README.md.
2026-04-27 12:26:58 -05:00
a1a7a6c829 forgejo: handle ifc:// markdown links in JS, eliminate required Go compile
- ifcurl.js: scan a[href^="ifc://"] at page load and replace with preview
  figures — works with [title](ifc://...) syntax without any Go extension
- viewer.js: Issue button now emits [title](ifc://...) markdown link syntax
  instead of a bare URL, so shared views render as previews without the patch
- README: document no-rebuild quick setup path; Go patch section is now optional

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:25:34 +01:00
27156a5906 forgejo: extract footer.tmpl inline JS into ifcurl.js asset
footer.tmpl now just loads /assets/ifcurl.js; all injection logic lives in
the new ifcurl.js file alongside the other viewer assets. Update deploy
instructions in both READMEs; fix incorrect /etc/forgejo path in top-level README.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:36:57 +01:00
7e9e814a4b viewer: add ifc:// URL links and clickable images to embedded snapshots
- ifc_url.go: wrap figcaption URL in <a href="ifc://"> for future protocol handler
- footer.tmpl: wrap diff snapshot images in viewer links; add ifc:// URL anchor below each snapshot (Cases 3 and 4)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:05:14 +01:00
36f528a254 viewer: extract inline JS into viewer.js
viewer.html was ~1250 lines of almost entirely JavaScript. The script
block is now a single <script type="module" src="./viewer.js"> tag,
leaving viewer.html as pure HTML/CSS (~238 lines).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 15:50:28 +01:00
3dd804ffc7 viewer: fix five P3 bugs (types, clip, ref nav, ortho, BCF snapshot)
- Types panel: fix count by iterating groupData directly (groupData.map
  was undefined — FragmentIdMap is the groupData value, not a wrapper)
- Clip planes: register OBC.Raycasters for the world before setupClipper
  so clipper.create(world) can raycast against the model surface
- Ref navigation: ▾ button now navigates when input already has a value;
  add change listener alongside input for cross-browser datalist coverage
- Orthographic projection: replace missing controls.setOrthoCamera() with
  world.camera.projection.set("Orthographic") + threeOrtho scale fix;
  refresh threeCamera reference after projection switch
- BCF export: capture rendererCanvas snapshot and include snapshot.png in
  the BCF zip with a <Snapshot> reference in markup.bcf

Closes: ifcurl-07d, ifcurl-1pa, ifcurl-66h, ifcurl-xy9, ifcurl-uo9

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 14:07:52 +01:00
63eaa63611 viewer: add 'All storeys' reset row to storey filter (ifcurl-0n8)
Add a permanent 'All storeys' row at the top of the Storeys list in the
model overview panel. It starts active (all visible) and clicking it
restores full model visibility. Clicking any storey row then back to
'All storeys' is now always possible, even after switching between storeys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 09:40:25 +01:00
58219fa2e3 viewer: fix copy-URL button position, selector width, and issue link protocol
- Move ⎘ copy-URL button immediately after the URL text box (ifcurl-2m9)
- Widen selector field from flex:1 to flex:2 (ifcurl-mts)
- Use window.location.origin for the new-issue link, removing hardcoded https:// (ifcurl-njq)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 08:49:47 +01:00
dd0fb2c365 viewer: fix load transition — snapshot appears before navigation blank
Show the canvas snapshot on the current page (z-index 9999) immediately
before navigating, so the browser's between-pages blank is hidden behind
the static image. Navigate without delay. Remove the body fadeIn animation
that was making the snapshot on the new page invisible during the 0.2s
fade-in, causing the visible blank-then-snapshot sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 08:29:32 +01:00
be1395649c viewer: fix visibility modes, add dropdown, zoom-all, and load transition
- visibility=highlight: apply orange colour overlay to selected elements
  (all others visible normally) using FragmentsManager.highlight() directly
  — OBC.Highlighter is in @thatopen/components-front which is not installed
- visibility=ghost: dim non-selected elements to 15% grey opacity
- visibility=isolate: unchanged (hider.isolate)
- Add 'show' dropdown (highlight/ghost/isolate) that appears when a
  selector is set; changing it reloads with the new visibility= param
- Add 'fit' button: calls cameraControls.fitToBox(model.box, true)
- Smooth load transition: capture canvas snapshot (preserveDrawingBuffer)
  before navigation, display it as an overlay on the new page, fade it
  out once the model has rendered

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 08:17:34 +01:00
c7c5d35c63 forgejo: regenerate go.patch with ServiceToken field
Amended forgejo commit now includes ServiceToken in the IfcURL config
struct. Regenerated patch from that commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 00:37:15 +01:00
7aa4ef85ab viewer: fix visibility=highlight — show all elements, highlight selection
Previously applySelector always called hider.isolate() which hid all
non-selected elements regardless of visibility mode. For highlight mode,
set up the Highlighter before applying the selector and use
highlightByID("select") instead so all elements remain visible with the
selection highlighted in orange.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 00:20:51 +01:00
134bd4d887 docs: fix asset deploy path in forgejo/README.md
Custom assets go to /var/lib/forgejo/custom/public/assets/, not
/etc/forgejo/public/assets/ which Forgejo does not serve from.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 23:12:15 +01:00
bcf9d58470 service: fix preview pipeline for local Forgejo deployment
- auth.py: catch OSError (not just FileNotFoundError) so ProtectHome=yes
  doesn't crash token lookup — returns None and falls back to anonymous
- git.py: wipe partial bare-clone dir before HTTP fallback so git can retry
  after HTTPS fails (port 443 not available on local HTTP-only Forgejo)
- pyproject.toml: add python-multipart to service deps (FastAPI form data)
- ifcurl-api.service: fix ExecStart path (/opt/ifcurl), set allowed-hosts
  to localhost, add MPLCONFIGDIR to silence matplotlib cache warning
- ifcurl-render.service: fix ExecStart path, add CacheDirectory + MPLCONFIGDIR
- nginx-ifcurl.conf: new file tracking the nginx reverse-proxy config;
  listen on [::]:80 so IPv6 loopback requests reach the Forgejo proxy block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 23:10:35 +01:00
6045df2bc6 viewer: inject render_diff thumbnail on commit page for .ifc changes
On /owner/repo/commit/{sha} pages, Case 4 injects an image below the
diff-file-header of each IFC file: render_diff for modifications,
plain preview for adds and deletes.  Parent SHA is read from the
existing "parent" commit link in the DOM — no extra API calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 21:07:07 +01:00
ab4fb6ded9 viewer: ref picker with branch/tag datalist and camera-preserving navigation
The ref input now has a datalist populated from the Forgejo branches+tags
API on load. A ▾ button clears and focuses the field to expose all refs.
Selecting a datalist option auto-navigates; pressing Enter in any structured
field now preserves camera/clip/fov/scale from the current URL so switching
refs keeps the same viewpoint.

Closes ifcurl-4rj.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:29:17 +01:00
e66460fb13 service+viewer: add /select endpoint for complex selector resolution
New GET+POST /select endpoint resolves an ifc:// URL selector to a JSON
list of GlobalIds, with T3 caching. The viewer now detects non-type-name
selectors (property filters, Name=, pset notation) and falls back to a
/select round-trip, applying the returned GUIDs via getLocalIdsByGuids().

Closes ifcurl-ov6.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:20:41 +01:00
14c8412074 viewer: add model metadata panel with type counts and storey isolation
A new '☰' toolbar button opens a panel showing the IFC type breakdown
(sorted by element count) and storey list. Clicking a storey isolates
its elements via OBC.Hider; clicking again restores full visibility.

Closes ifcurl-dag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:04:43 +01:00
a5d14222a5 viewer: show commit-pin badge + branch-switch button when ref is a hash
When an ifc:// URL uses a bare commit hash as the ref the toolbar now
shows an amber badge with the abbreviated hash and (after a Forgejo API
lookup) a '→ heads/main' button that reloads with the branch-tip ref.

Closes ifcurl-gy8.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:19:37 +01:00
fce198580a viewer: upgrade three/web-ifc/@thatopen to latest — partial CVE fix
three 0.177→0.184, web-ifc 0.0.75→0.0.77, @thatopen/components
3.3.1→3.4.2, @thatopen/fragments 3.3.1→3.4.3, + new camera-controls
3.1.2 peer dep. Reduces fast-xml-parser findings from 2 critical to
1 high + 1 low; remaining issues are upstream in @thatopen >= 3.3.3
with no fix released yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 18:59:02 +01:00
4ef7987ad1 viewer: bundle JS deps locally — removes all CDN dependencies
Replace importmap pointing at esm.sh/unpkg with locally-bundled files
built from pinned npm packages (package.json + lockfile). Run
`cd forgejo && npm install && npm run build` to regenerate after a
version bump. Tracked in ifcurl-dkz: fast-xml-parser CVEs in
@thatopen/components 3.3.1 should be addressed in a follow-up upgrade.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 18:24:50 +01:00
c6ffc4009c viewer: BCF export with selector delegates to /bcf service for component GUIDs
Client-side BCF generation cannot resolve IFC GlobalIds from a selector
string.  When a selector is present, POST to /bcf instead — the service
runs the selector through IfcOpenShell and writes resolved GUIDs into the
viewpoint.  Fails visibly if unreachable; no silent fallback to a GUIDless
file.  Also adds /bcf to the Nginx proxy block in forgejo/README.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:54:20 +01:00
6b6e47ef54 Split service into ifcurl-api + ifcurl-render for RCE isolation
render_service.py: new FastAPI app with /render, /select, /render_diff
endpoints that wrap run_sandboxed — listens on a Unix domain socket.

service.py: imports the three pipeline functions from render_service.py;
delegates to render service via httpx when IFCURL_RENDER_SOCKET is set,
falls back to direct run_sandboxed when unset (single-service mode).

__main__.py: add 'render-service' subcommand (--socket PATH).

pyproject.toml: add httpx to service optional deps.

ifcurl-api.service: renamed from ifcurl-preview.service; sets
IFCURL_RENDER_SOCKET, adds AF_UNIX to RestrictAddressFamilies.

ifcurl-render.service: new hardened unit — AF_UNIX only, ~execve
~execveat blocked, no credentials, separate ifcurl-render user.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 10:00:33 +01:00
fef6c16946 ifcurl-preview.service: correct misleading execve comment
execve is actually permitted — it arrives via @default which is included in
@system-service.  The main service needs it for GitPython's git CLI calls.
Blocking execve only in render subprocesses would require per-process seccomp,
which is not worth the complexity for this threat model.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 08:30:41 +01:00
8e474f2629 gitconfig-ifcmerge: use --prioritise-local correctly per merge direction
ifcmerge defaults to preserving remote IDs; --prioritise-local preserves local
IDs instead.  For Forgejo merge commits, %A is main (local) so ifcmerge_ours
(--prioritise-local) is the right driver — main's IDs are preserved and the
PR branch is renumbered.  For developer rebases from main, %B is main (remote)
so the plain ifcmerge driver (no flag) preserves main's IDs by default.

Update /etc/gitattributes to use ifcmerge_ours for server-side merges, and
update README with a direction table clarifying which driver to use when.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 07:41:49 +01:00
6f23d874d3 forgejo/README.md: front-load feature/dependency matrix
Makes explicit which features need the Go patch (markdown preview only) vs
which are static assets that survive Forgejo upgrades without recompilation.
Documents that the Nginx proxy is required for PR diff images because the
browser fetches them and can't reach localhost directly, and notes that most
production deployments already have Nginx for TLS anyway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 07:26:00 +01:00
ec461239f4 forgejo: PR diff page renders IFC diff images via footer.tmpl + Nginx proxy
Case 3 in footer.tmpl detects /{owner}/{repo}/pulls/{N}/files, fetches base/
head SHAs from the Forgejo API, then injects <img src="/render_diff?…"> below
the file header of each .ifc file in the diff.  Returns early so Case 2 (history
page) does not also run on the PR diff page.

README documents the required Nginx proxy for /preview and /render_diff, and
clarifies that PREVIEW_SERVICE_URL is server-side-only (localhost:8000 is
correct for it); the browser-side diff images need the proxy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 07:17:17 +01:00
275e74cce8 ifcurl-preview.service: fix cache path for non-root service user
Use CacheDirectory=ifcurl (systemd creates /var/cache/ifcurl owned by the
service user) and XDG_CACHE_HOME=/var/cache so platformdirs resolves to
that path.  Remove the /root/.cache/ifcurl ReadWritePaths entry which was
inaccessible to the unprivileged ifcurl user.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:48:58 +01:00
df92114cda ifcurl-preview.service: systemd sandbox hardening
Add sandboxing directives to limit blast radius from a compromised render
subprocess:
- NoNewPrivileges + RestrictSUIDSGID: no privilege escalation
- ProtectSystem=strict + ProtectHome: Forgejo data tree is unwritable
- PrivateTmp: isolated /tmp
- RestrictAddressFamilies=AF_INET AF_INET6: no Unix socket access to Forgejo
- SystemCallFilter=@system-service @process @files @io-event @network-io:
  allows fork/clone (needed by ifcopenshell geom workers) but excludes
  execve/execveat so an attacker cannot exec a shell
- RestrictNamespaces, LockPersonality, ProtectKernelTunables, etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:47:48 +01:00
ee317bef75 footer.tmpl: View in 3D on history page; urlTerminators fix; selector docs
- footer.tmpl: refactor to inject per-commit "View in 3D" links on the file
  history page, alongside the existing file view page button
- ifc_url.go: add ')' to urlTerminators so bare ifc:// URLs followed by a
  closing parenthesis are correctly terminated
- ifc_url_test.go: add tests for @ in query value, ) terminator, trailing
  period, and SERVICE_TOKEN presence/absence in preview URL
- viewer.html: selector input placeholder and title attribute now document
  that the browser viewer only supports type-name union selectors
- README: document viewer selector limitation vs full server-side behaviour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 15:45:31 +01:00
39a2786872 Private repo previews, BCF import, SERVICE_TOKEN support
- GET /preview now accepts a token= query parameter, matching POST /preview
- Forgejo Go: add SERVICE_TOKEN config field; renderer appends it to <img src>
  preview URLs so a machine-user token can authenticate private repos
- Go tests: verify token appears in preview URL when set, absent when unset
- viewer.html: BCF import via drag-and-drop or Import .bcf button in BCF row;
  parses first viewpoint camera/clips/GUIDs and reloads with the viewpoint
  applied to the current model context
- viewer-url.js, viewer.html: unescape %24 → $ in URL building so GlobalIds
  with $ characters are human-readable in ifc:// URLs
- README: document SERVICE_TOKEN and private-repo preview configuration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 14:20:17 +01:00
63c0cc7ada viewer.html: copy URL, create issue, element properties, load progress
- Add ⎘ button to copy the current ifc:// URL to clipboard
- Add Issue button to open a pre-filled new issue on the git host
- Add click-to-identify via OBC.Highlighter: clicking an element shows
  its IFC type, name, and GlobalId in a panel with a copy button
- Replace arrayBuffer() with ReadableStream loop showing fetch progress
  as percentage (when Content-Length known) or MB transferred
- README: document viewer features, collaboration workflow, and that
  private Forgejo repos work via same-origin session cookie
- CLAUDE.md: document that [attr=val] bracket selector syntax is invalid;
  correct form is comma-separated (IfcWall, Name="...") and bare GlobalId

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:50:26 +01:00
01de9fdcac Fix 7 issues from deep review
- pyproject.toml: add bcf to [service] dependencies (ifcurl-9rk)
- README.md: replace bogus GET /viewer row with POST /bcf (ifcurl-ttb)
- viewer-url.js: fix GitLab host detection (includes → startsWith) (ifcurl-3n4)
- git.py: call _evict_if_needed() after fetch, not only after clone (ifcurl-t1k)
- bcf.py: add description param to build_bcf(); markup.bcf now includes
  <Description> when provided (ifcurl-7wg)
- service.py: pass request URL as description to build_bcf() (ifcurl-7wg)
- viewer.html: BCF export reads visibility= from URL and sets
  DefaultVisibility accordingly (ifcurl-sto)
- viewer.html: applyCameraParam now switches to orthographic projection
  and applies scale when scale= is present in URL (ifcurl-4l7)
- tests: 3 new tests for description field; update JS tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 11:57:06 +01:00
dae1435519 Add BCF export: /bcf endpoint, build_bcf(), viewer panel
- ifcurl/bcf.py: build_bcf() builds BCF 2.1 zips with perspective/
  orthographic camera, clipping planes, component selection, and
  isolate visibility mode
- ifcurl/service.py: /bcf POST endpoint parses ifc:// URL, resolves
  selector → GUIDs when present, delegates to build_bcf()
- viewer.html: BCF toolbar button + collapsible title/comment form;
  client-side zip generation via JSZip (esm.sh CDN) so the browser
  does not need service access
- tests/test_bcf.py: 21 tests covering build_bcf() unit behaviour and
  the /bcf endpoint including SSRF rejection and selector resolution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 09:59:22 +01:00
27883e468e forgejo: document ifcmerge mergeability — bare repo gitattributes finding
Investigation confirmed: Forgejo's git merge-tree --write-tree (git ≥2.38)
does invoke configured merge drivers for .ifc files.

Key finding: bare repositories do NOT read committed .gitattributes — the
merge driver is silently skipped without a system-wide core.attributesFile.

Changes:
- server-config/gitattributes: new file to deploy at /etc/gitattributes,
  containing "*.ifc merge=ifcmerge"
- server-config/gitconfig-ifcmerge: adds [core] attributesFile = /etc/gitattributes
  and explains the bare-repo constraint
- forgejo/README.md: rewrites ifcmerge section with investigation findings,
  deployment steps, and distinction between server-side vs client-side setup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 09:27:56 +01:00
e824f9fd0e forgejo: systemd unit, ifcmerge driver docs; fix stale proxy mention
- forgejo/server-config/ifcurl-preview.service: systemd unit for the preview
  service with dedicated user, EnvironmentFile for cache config, and
  --allowed-hosts in ExecStart
- forgejo/server-config/gitconfig-ifcmerge: registers two [merge] driver
  variants (ifcmerge and ifcmerge_ours) for the asymmetric .ifc merge case
- forgejo/README.md: systemd deployment section (manual + service), ifcmerge
  section covering Perl script install, driver registration, per-repo
  gitattributes, and merge-direction constraint (merge-commit preserves main's IDs)
- README.md: remove stale "IFC file proxy" description; update quick-summary
  to show systemd install path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 09:17:32 +01:00
7b95551cb8 forgejo: add MIT SPDX license headers to JS/HTML assets
viewer.html, viewer-url.js, and footer.tmpl are intended for Forgejo
contribution and carry MIT headers matching Forgejo's per-file convention.
ThatOpen @thatopen/components (loaded from CDN) is also MIT-licensed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 08:30:56 +01:00
d076196bac service.py: SSRF protection — reject local transport, add --allowed-hosts
The preview endpoint is co-hosted with a specific Forgejo instance, so it
should only be allowed to fetch from that host.

- Local file transport (ifc:///path) is always rejected with 403 — the service
  must not read from the server's own filesystem
- New --allowed-hosts CLI option for `ifcurl serve` takes a comma-separated
  list of permitted git hostnames (e.g. git.example.com or localhost:3000);
  any unlisted host is rejected with 403
- Defense-in-depth: literal private/loopback/link-local IP addresses are
  blocked even without an allowlist (169.254.x.x, 127.x, RFC1918, etc.)
- README and forgejo/README.md updated to include --allowed-hosts in the
  standard deployment invocation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 08:26:37 +01:00
e02ba0e214 forgejo: complete patch directory with apply instructions
Add the missing Go source files and a README covering the full apply
procedure. Previously only custom assets and the test file were tracked
here; ifc_url.go and the two-file diff were only in the Forgejo tree.

- forgejo/modules/markup/markdown/ifc_url.go — goldmark extension source
- forgejo/go.patch — diff for markdown.go + markup.go (7 lines total)
- forgejo/README.md — step-by-step: apply patch, run tests, build,
  deploy assets, configure app.ini, upgrade procedure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:57:33 +01:00
995218bb4e Add Go tests for ifc_url.go goldmark extension
7 tests in forgejo/modules/markup/markdown/ifc_url_test.go covering:
link form, bare URL, bare URL with underscores (sibling-node reassembly),
disabled when no service URL configured, HTML escaping, non-ifc links
unchanged, and inline-among-text placement.

Tests use goldmark.New() directly — no Forgejo DB/git test infrastructure
needed. Only dependencies are setting.IfcURL (ours) and goldmark (stable).
File lives in the same package as ifc_url.go and is part of the patch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:51:46 +01:00
d98d4c35d5 viewer: extract URL logic to viewer-url.js, add JS tests
parseIfcUrl, buildIfcUrl, toRawUrl extracted from viewer.html into
viewer-url.js so they can be imported and tested independently.
viewer.html imports from the module. 32 Node test-runner tests in
tests/test_viewer_url.mjs cover all three functions across GitHub,
GitLab, Forgejo/Gitea, SSH user@ prefix, and round-trip cases.

Caught a bug: + (selector union operator) was being URL-encoded as
%2B in buildIfcUrl; fixed by adding .replace(/%2B/g, "+") alongside
the existing comma fix, in both viewer-url.js and syncCameraUrl.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:31:19 +01:00
13c8687027 viewer.html: interactive clipping plane tool
Replaces the raw renderer.clippingPlanes approach with OBC.Clipper:
URL-specified clip= planes are seeded as draggable SimplePlanes.
Toolbar '✂ clip' toggle + double-click on model surface to add planes;
'⊗ clips' button (shown when planes exist) to clear all. syncCameraUrl
now serialises current plane state back to the ifc:// URL on every
camera move or plane drag/create/delete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:24:06 +01:00
a33a4f9296 viewer.html: implement clip= clipping planes
applyClipPlanes() parses all repeatable clip= params (6 floats: point +
normal in IFC world coords), transforms each via toThree() into Three.js
space, and sets world.renderer.three.clippingPlanes. syncCameraUrl already
preserves clip= params when rebuilding the URL on camera movement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:10:19 +01:00