Repository navigation
GraphPaper 0.6: ink-and-paper UI and editable spatial knowledge graphs - #10
Conversation
… edits use the connector
…ed Windows checks
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb03077f0c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function gsConnectDialog(from,to){gsGuard(()=>showModal('Connect two ideas','Direction and relationship are yours to specify.',`<label for="gs-new-source">From</label><select id="gs-new-source">${gsEndpointOptions(from||state.node||state.p.graph.nodes[0]?.id)}</select><label class="mt12" for="gs-new-target">To</label><select id="gs-new-target">${gsEndpointOptions(to||state.p.graph.nodes[1]?.id)}</select><label class="mt12" for="gs-new-relation">Relationship</label><input id="gs-new-relation" list="gs-relations" value="related_to" maxlength="80">${gsRelationList()}<label class="mt12" for="gs-new-notes">Notes</label><textarea id="gs-new-notes" maxlength="2000"></textarea>`,btn('Create connection','gs-create-edge','branch','primary')));} | ||
| const gsPreviousAction=handleAction; | ||
| handleAction=async function(el){const a=el.dataset.action; | ||
| if(GS.dirty&&['tab','home','new','open-project','run-graph','run-angles','pin','unpin','exclude','clear-exclusions','brief'].includes(a)){gsGuard(()=>gsPreviousAction(el));return;} |
There was a problem hiding this comment.
Guard every rerendering action while graph fields are dirty
When an inspector field has unsaved changes, actions omitted from this allowlist can still invoke the original handler and rerender the application. For example, saving a project rename, saving settings, or changing a source role calls render() in ui/app.js, replacing the inspector with stored graph values and silently discarding the user's edits; GS.dirty remains true, so a later save prompt cannot recover the lost values. Route all actions that can rerender or replace project state through gsGuard while the inspector is dirty.
Useful? React with 👍 / 👎.
| let candidates=g.nodes.filter(n=>(!o.kind||kind(n.kind)===o.kind)&&(!o.focus||neighborhood.has(n.id))&&(!search||matched.has(n.id))); | ||
| const rank=n=>(n.id===o.selected?1e7:0)+(path.has(n.id)?1e6:0)+(pins.has(n.id)?1e5:0)+(degree.get(n.id)||0); | ||
| candidates.sort((a,b)=>rank(b)-rank(a)||String(a.id).localeCompare(String(b.id))); | ||
| const nodes=candidates.slice(0,limit),ids=new Set(nodes.map(n=>n.id)); |
There was a problem hiding this comment.
Apply relationship filters before enforcing the node budget
On graphs larger than the selected node limit, nodes are ranked and truncated before o.relation is applied to the edges. If all endpoints of the requested relationship family are low-degree nodes outside the initial 60/100/180-node slice, selecting that relationship reports zero connections even though matching edges exist in the graph. Filter or prioritize the matching edges' endpoints before slicing the node candidates so the connection filter can expose matches anywhere in the graph.
Useful? React with 👍 / 👎.
| function gsEdgeStyle(value){const family=GraphMath.relation(value);if(family!=='association')return {...GS_REL[family],key:family};let h=0;for(const c of String(value||''))h=(h*31+c.charCodeAt(0))>>>0;const index=h%GS_OTHER.length;return {name:String(value),color:GS_OTHER[index],dash:['2 5','7 4','1 3','8 3 2 3','4 4',''][index],key:'other-'+index};} | ||
| function gsViewRender(){const inspector=$('.gs-inspector');render();if(GS.dirty&&inspector&&$('.gs-inspector'))$('.gs-inspector').replaceWith(inspector);} | ||
| function gsDefaults(){return {mode:'3d',yaw:.28,pitch:-.15,zoom:1,panX:0,panY:0,limit:60,kind:'',relation:'',hops:0,spread:1,fit:true};} | ||
| function gsEnsure(){if(!state.p)return;if(GS.pid===state.p.id)return;GS.pid=state.p.id;GS.view=gsDefaults();GS.positions=Object.create(null);GS.dirty=false;GS.linkFrom=null;GS.canUndo=false;GS.drag=null;try{const saved=JSON.parse(storage.getItem('graphpaper.spatial.'+GS.pid)||'null');if(saved){for(const key of ['yaw','pitch','zoom','panX','panY','spread'])if(Number.isFinite(saved.view?.[key]))GS.view[key]=GraphMath.clamp(saved.view[key],key==='zoom'?.12:-1e5,key==='zoom'?5:1e5);GS.view.spread=GraphMath.clamp(GS.view.spread,.5,3);GS.view.mode=saved.view?.mode==='2d'?'2d':'3d';GS.view.fit=false;for(const [id,p] of Object.entries(saved.positions||{}).slice(0,5000))if(['x','y','z'].every(k=>Number.isFinite(p[k])&&Math.abs(p[k])<1e6))GS.positions[id]=p;}}catch{}const pid=GS.pid;api('/projects/'+pid+'/graph/edit-status').then(s=>{if(GS.pid===pid){GS.canUndo=s.can_undo;const b=$('[data-action="gs-undo"]');if(b)b.disabled=!GS.canUndo||busy();}}).catch(()=>{});} |
There was a problem hiding this comment.
Refresh undo availability after replacing the graph
gsEnsure only fetches edit status when the project ID changes, so GS.canUndo remains true after the same project's graph is imported or rebuilt. In that scenario the rendered Undo button stays enabled even though the backend correctly rejects the old history because its after_hash no longer matches the replacement graph, leaving users with an action that consistently returns a conflict. Recheck or clear undo state whenever graph contents are replaced.
Useful? React with 👍 / 👎.
Implemented
The real application UI now has charcoal/ink-blue surfaces, an offline-bundled scan of actual squared graph paper in the sidebar, and a spatial knowledge-graph editor. The scan is Calsidyrose's licensed CC BY 2.0 image with visible attribution.
Verified
Successful pre-merge release-candidate run: https://github.com/AronAxe/GraphPaper/actions/runs/37806576162 . Exact tested revision:
fb03077f0cb5c332e6d47b50f13a18d6428c7fb4.Quality matrix: https://github.com/AronAxe/GraphPaper/actions/runs/37806575973 . Windows/Linux Python 3.12/3.13 and browser jobs passed. Documentation checks passed.
All changes and tests use a disposable ChatGPT working container and GitHub-hosted runners. No access to the user's computer, manuscripts, credentials or model accounts. No image generation. Provider responses are synthetic fixtures, not a claim of live writing quality validation.
Release
Merge triggers the main-branch v0.6 workflow, which revalidates and publishes only the archive that passes compiled-native checks and uploaded-checksum verification. Temporary source-transfer files/workflows have been removed from the final tree; the old v0.5 release workflow is superseded.