Skip to content

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike - #428

Merged
rejojer merged 3 commits into
mainfrom
fix/client-config-followups
Aug 26, 2026
Merged

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike#428
rejojer merged 3 commits into
mainfrom
fix/client-config-followups

Conversation

@rejojer

Copy link
Copy Markdown
Member

Three follow-ups to #424, on the shipped v0.2.11.

.env search ends at the cwd tree.find_dotenv(usecwd=True) returns '' when nothing is reachable from the cwd, and or None turned that into load_dotenv's own upward walk from utils.py — the install-dir search the cwd search was added to replace. A pip-installed SDK could silently load another project's .env from above site-packages. Test plants a .env above a symlinked package and runs from a tree that has none.

A local client with a blank chat_model refuses at the chat door._local_chat reads blank as "managed chat", which a client without an api_key does not have: chat_completions() reached for LocalAPI.chat_completions and raised a bare AttributeError. The managed branch now raises a PageIndexAPIError naming chat_model.

storage_path is typed str | os.PathLike[str]._ARG_TYPES accepts os.PathLike, so Path(...) ran fine, and py.typed made the Optional[str] annotation authoritative for callers' type checkers. Both signatures and LocalIndexConfig now agree with the runtime check.

Also on the branch: index= / chat= typed Mapping[str, Any] so the exported config shapes pass, plus a comment and two docstrings that stop overclaiming.

434 tests (432 + 2, both red on main). A review view of #424 + these follow-ups together is open against pre-424-main.

https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb

… a blank chat_model refuses at the chat door; storage_path is typed PathLike
find_dotenv(usecwd=True) returns '' when nothing is reachable from the
cwd, and `or None` turned that into load_dotenv's own upward walk from
utils.py — the install-dir leak the cwd search was added to replace. A
pip-installed SDK could load another project's .env from above
site-packages, silently.
_local_chat treats a blank chat_model as "managed chat", which a client
without an api_key does not have: chat_completions() then reached for
LocalAPI.chat_completions and raised a bare AttributeError. The managed
branch now refuses as a PageIndexAPIError naming chat_model.
py.typed made the annotations authoritative while storage_path was typed
str; _ARG_TYPES accepts os.PathLike, so Path(...) ran fine and failed the
user's type check. Both signatures and LocalIndexConfig now say so.
Claude-Session: https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb
… two docstrings stop overclaiming
The slots were annotated dict[str, Any]. A TypedDict is consistent with
Mapping[str, object], never with dict (PEP 589: a dict-typed receiver
could write arbitrary keys through it), so the four shapes types.py
exports — and py.typed advertises to installed callers' checkers —
could not be passed to the one place they describe. pyright on a probe
that does exactly that: 9 errors before, 0 after. The constructor only
reads the slot (items(), then a fresh conf dict), so Mapping is the
honest bound; a plain dict is a Mapping, and TypedDict instances are
plain dicts at runtime, so nothing moves at runtime.
The _ARG_TYPES comment said "every value" is shape-checked; api_key is
not in the table (its empty check is separate, its type check stays
unchecked by ruling), so the comment now speaks for the table only.
_local_doc_scope and _require_local_scope still explained the cloud
drop as "scoping is server-side" — true of the managed chat, which
never reaches either function. What reaches them on a cloud client is
own-model chat and the config helpers, whose cloud tools take no
allowlist: targeting there is prompt-level only, as the error message
between them already said.
434 passed; pyright on pageindex/ unchanged at 235 (0 in the touched
files, before and after).
Claude-Session: https://claude.ai/code/session_01TxG8u8x29XRnK4yscZVCch
@rejojer
rejojer merged commit b9a9a3b into mainAug 26, 2026
14 checks passed
rejojer added a commit that referenced this pull request Aug 26, 2026
Two post-0.2.11 follow-up squashes (b9a9a3b, 174f95f) land on the review
branch the same way f6fa99b did: main's tree recorded as merged, history
kept, so PR #400 keeps spanning 0.2.9 → current main.
Claude-Session: https://claude.ai/code/session_01VQ6mruXZBgw9Hjii8KPbQP
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike by rejojer · Pull Request #428 · VectifyAI/PageIndex · GitHub
Skip to content

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike - #428

Merged
rejojer merged 3 commits into
mainfrom
fix/client-config-followups
Aug 26, 2026
Merged

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike#428
rejojer merged 3 commits into
mainfrom
fix/client-config-followups

Conversation

@rejojer

Copy link
Copy Markdown
Member

Three follow-ups to #424, on the shipped v0.2.11.

.env search ends at the cwd tree.find_dotenv(usecwd=True) returns '' when nothing is reachable from the cwd, and or None turned that into load_dotenv's own upward walk from utils.py — the install-dir search the cwd search was added to replace. A pip-installed SDK could silently load another project's .env from above site-packages. Test plants a .env above a symlinked package and runs from a tree that has none.

A local client with a blank chat_model refuses at the chat door._local_chat reads blank as "managed chat", which a client without an api_key does not have: chat_completions() reached for LocalAPI.chat_completions and raised a bare AttributeError. The managed branch now raises a PageIndexAPIError naming chat_model.

storage_path is typed str | os.PathLike[str]._ARG_TYPES accepts os.PathLike, so Path(...) ran fine, and py.typed made the Optional[str] annotation authoritative for callers' type checkers. Both signatures and LocalIndexConfig now agree with the runtime check.

Also on the branch: index= / chat= typed Mapping[str, Any] so the exported config shapes pass, plus a comment and two docstrings that stop overclaiming.

434 tests (432 + 2, both red on main). A review view of #424 + these follow-ups together is open against pre-424-main.

https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb

… a blank chat_model refuses at the chat door; storage_path is typed PathLike
find_dotenv(usecwd=True) returns '' when nothing is reachable from the
cwd, and `or None` turned that into load_dotenv's own upward walk from
utils.py — the install-dir leak the cwd search was added to replace. A
pip-installed SDK could load another project's .env from above
site-packages, silently.
_local_chat treats a blank chat_model as "managed chat", which a client
without an api_key does not have: chat_completions() then reached for
LocalAPI.chat_completions and raised a bare AttributeError. The managed
branch now refuses as a PageIndexAPIError naming chat_model.
py.typed made the annotations authoritative while storage_path was typed
str; _ARG_TYPES accepts os.PathLike, so Path(...) ran fine and failed the
user's type check. Both signatures and LocalIndexConfig now say so.
Claude-Session: https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb
… two docstrings stop overclaiming
The slots were annotated dict[str, Any]. A TypedDict is consistent with
Mapping[str, object], never with dict (PEP 589: a dict-typed receiver
could write arbitrary keys through it), so the four shapes types.py
exports — and py.typed advertises to installed callers' checkers —
could not be passed to the one place they describe. pyright on a probe
that does exactly that: 9 errors before, 0 after. The constructor only
reads the slot (items(), then a fresh conf dict), so Mapping is the
honest bound; a plain dict is a Mapping, and TypedDict instances are
plain dicts at runtime, so nothing moves at runtime.
The _ARG_TYPES comment said "every value" is shape-checked; api_key is
not in the table (its empty check is separate, its type check stays
unchecked by ruling), so the comment now speaks for the table only.
_local_doc_scope and _require_local_scope still explained the cloud
drop as "scoping is server-side" — true of the managed chat, which
never reaches either function. What reaches them on a cloud client is
own-model chat and the config helpers, whose cloud tools take no
allowlist: targeting there is prompt-level only, as the error message
between them already said.
434 passed; pyright on pageindex/ unchanged at 235 (0 in the touched
files, before and after).
Claude-Session: https://claude.ai/code/session_01TxG8u8x29XRnK4yscZVCch
@rejojer
rejojer merged commit b9a9a3b into mainAug 26, 2026
14 checks passed
rejojer added a commit that referenced this pull request Aug 26, 2026
Two post-0.2.11 follow-up squashes (b9a9a3b, 174f95f) land on the review
branch the same way f6fa99b did: main's tree recorded as merged, history
kept, so PR #400 keeps spanning 0.2.9 → current main.
Claude-Session: https://claude.ai/code/session_01VQ6mruXZBgw9Hjii8KPbQP
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike by rejojer · Pull Request #428 · VectifyAI/PageIndex · GitHub
Skip to content

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike - #428

Merged
rejojer merged 3 commits into
mainfrom
fix/client-config-followups
Aug 26, 2026
Merged

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike#428
rejojer merged 3 commits into
mainfrom
fix/client-config-followups

Conversation

@rejojer

Copy link
Copy Markdown
Member

Three follow-ups to #424, on the shipped v0.2.11.

.env search ends at the cwd tree.find_dotenv(usecwd=True) returns '' when nothing is reachable from the cwd, and or None turned that into load_dotenv's own upward walk from utils.py — the install-dir search the cwd search was added to replace. A pip-installed SDK could silently load another project's .env from above site-packages. Test plants a .env above a symlinked package and runs from a tree that has none.

A local client with a blank chat_model refuses at the chat door._local_chat reads blank as "managed chat", which a client without an api_key does not have: chat_completions() reached for LocalAPI.chat_completions and raised a bare AttributeError. The managed branch now raises a PageIndexAPIError naming chat_model.

storage_path is typed str | os.PathLike[str]._ARG_TYPES accepts os.PathLike, so Path(...) ran fine, and py.typed made the Optional[str] annotation authoritative for callers' type checkers. Both signatures and LocalIndexConfig now agree with the runtime check.

Also on the branch: index= / chat= typed Mapping[str, Any] so the exported config shapes pass, plus a comment and two docstrings that stop overclaiming.

434 tests (432 + 2, both red on main). A review view of #424 + these follow-ups together is open against pre-424-main.

https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb

… a blank chat_model refuses at the chat door; storage_path is typed PathLike
find_dotenv(usecwd=True) returns '' when nothing is reachable from the
cwd, and `or None` turned that into load_dotenv's own upward walk from
utils.py — the install-dir leak the cwd search was added to replace. A
pip-installed SDK could load another project's .env from above
site-packages, silently.
_local_chat treats a blank chat_model as "managed chat", which a client
without an api_key does not have: chat_completions() then reached for
LocalAPI.chat_completions and raised a bare AttributeError. The managed
branch now refuses as a PageIndexAPIError naming chat_model.
py.typed made the annotations authoritative while storage_path was typed
str; _ARG_TYPES accepts os.PathLike, so Path(...) ran fine and failed the
user's type check. Both signatures and LocalIndexConfig now say so.
Claude-Session: https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb
… two docstrings stop overclaiming
The slots were annotated dict[str, Any]. A TypedDict is consistent with
Mapping[str, object], never with dict (PEP 589: a dict-typed receiver
could write arbitrary keys through it), so the four shapes types.py
exports — and py.typed advertises to installed callers' checkers —
could not be passed to the one place they describe. pyright on a probe
that does exactly that: 9 errors before, 0 after. The constructor only
reads the slot (items(), then a fresh conf dict), so Mapping is the
honest bound; a plain dict is a Mapping, and TypedDict instances are
plain dicts at runtime, so nothing moves at runtime.
The _ARG_TYPES comment said "every value" is shape-checked; api_key is
not in the table (its empty check is separate, its type check stays
unchecked by ruling), so the comment now speaks for the table only.
_local_doc_scope and _require_local_scope still explained the cloud
drop as "scoping is server-side" — true of the managed chat, which
never reaches either function. What reaches them on a cloud client is
own-model chat and the config helpers, whose cloud tools take no
allowlist: targeting there is prompt-level only, as the error message
between them already said.
434 passed; pyright on pageindex/ unchanged at 235 (0 in the touched
files, before and after).
Claude-Session: https://claude.ai/code/session_01TxG8u8x29XRnK4yscZVCch
@rejojer
rejojer merged commit b9a9a3b into mainAug 26, 2026
14 checks passed
rejojer added a commit that referenced this pull request Aug 26, 2026
Two post-0.2.11 follow-up squashes (b9a9a3b, 174f95f) land on the review
branch the same way f6fa99b did: main's tree recorded as merged, history
kept, so PR #400 keeps spanning 0.2.9 → current main.
Claude-Session: https://claude.ai/code/session_01VQ6mruXZBgw9Hjii8KPbQP
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike by rejojer · Pull Request #428 · VectifyAI/PageIndex · GitHub
Skip to content

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike - #428

Merged
rejojer merged 3 commits into
mainfrom
fix/client-config-followups
Aug 26, 2026
Merged

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike#428
rejojer merged 3 commits into
mainfrom
fix/client-config-followups

Conversation

@rejojer

Copy link
Copy Markdown
Member

Three follow-ups to #424, on the shipped v0.2.11.

.env search ends at the cwd tree.find_dotenv(usecwd=True) returns '' when nothing is reachable from the cwd, and or None turned that into load_dotenv's own upward walk from utils.py — the install-dir search the cwd search was added to replace. A pip-installed SDK could silently load another project's .env from above site-packages. Test plants a .env above a symlinked package and runs from a tree that has none.

A local client with a blank chat_model refuses at the chat door._local_chat reads blank as "managed chat", which a client without an api_key does not have: chat_completions() reached for LocalAPI.chat_completions and raised a bare AttributeError. The managed branch now raises a PageIndexAPIError naming chat_model.

storage_path is typed str | os.PathLike[str]._ARG_TYPES accepts os.PathLike, so Path(...) ran fine, and py.typed made the Optional[str] annotation authoritative for callers' type checkers. Both signatures and LocalIndexConfig now agree with the runtime check.

Also on the branch: index= / chat= typed Mapping[str, Any] so the exported config shapes pass, plus a comment and two docstrings that stop overclaiming.

434 tests (432 + 2, both red on main). A review view of #424 + these follow-ups together is open against pre-424-main.

https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb

… a blank chat_model refuses at the chat door; storage_path is typed PathLike
find_dotenv(usecwd=True) returns '' when nothing is reachable from the
cwd, and `or None` turned that into load_dotenv's own upward walk from
utils.py — the install-dir leak the cwd search was added to replace. A
pip-installed SDK could load another project's .env from above
site-packages, silently.
_local_chat treats a blank chat_model as "managed chat", which a client
without an api_key does not have: chat_completions() then reached for
LocalAPI.chat_completions and raised a bare AttributeError. The managed
branch now refuses as a PageIndexAPIError naming chat_model.
py.typed made the annotations authoritative while storage_path was typed
str; _ARG_TYPES accepts os.PathLike, so Path(...) ran fine and failed the
user's type check. Both signatures and LocalIndexConfig now say so.
Claude-Session: https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb
… two docstrings stop overclaiming
The slots were annotated dict[str, Any]. A TypedDict is consistent with
Mapping[str, object], never with dict (PEP 589: a dict-typed receiver
could write arbitrary keys through it), so the four shapes types.py
exports — and py.typed advertises to installed callers' checkers —
could not be passed to the one place they describe. pyright on a probe
that does exactly that: 9 errors before, 0 after. The constructor only
reads the slot (items(), then a fresh conf dict), so Mapping is the
honest bound; a plain dict is a Mapping, and TypedDict instances are
plain dicts at runtime, so nothing moves at runtime.
The _ARG_TYPES comment said "every value" is shape-checked; api_key is
not in the table (its empty check is separate, its type check stays
unchecked by ruling), so the comment now speaks for the table only.
_local_doc_scope and _require_local_scope still explained the cloud
drop as "scoping is server-side" — true of the managed chat, which
never reaches either function. What reaches them on a cloud client is
own-model chat and the config helpers, whose cloud tools take no
allowlist: targeting there is prompt-level only, as the error message
between them already said.
434 passed; pyright on pageindex/ unchanged at 235 (0 in the touched
files, before and after).
Claude-Session: https://claude.ai/code/session_01TxG8u8x29XRnK4yscZVCch
@rejojer
rejojer merged commit b9a9a3b into mainAug 26, 2026
14 checks passed
rejojer added a commit that referenced this pull request Aug 26, 2026
Two post-0.2.11 follow-up squashes (b9a9a3b, 174f95f) land on the review
branch the same way f6fa99b did: main's tree recorded as merged, history
kept, so PR #400 keeps spanning 0.2.9 → current main.
Claude-Session: https://claude.ai/code/session_01VQ6mruXZBgw9Hjii8KPbQP
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike by rejojer · Pull Request #428 · VectifyAI/PageIndex · GitHub
Skip to content

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike - #428

Merged
rejojer merged 3 commits into
mainfrom
fix/client-config-followups
Aug 26, 2026
Merged

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike#428
rejojer merged 3 commits into
mainfrom
fix/client-config-followups

Conversation

@rejojer

Copy link
Copy Markdown
Member

Three follow-ups to #424, on the shipped v0.2.11.

.env search ends at the cwd tree.find_dotenv(usecwd=True) returns '' when nothing is reachable from the cwd, and or None turned that into load_dotenv's own upward walk from utils.py — the install-dir search the cwd search was added to replace. A pip-installed SDK could silently load another project's .env from above site-packages. Test plants a .env above a symlinked package and runs from a tree that has none.

A local client with a blank chat_model refuses at the chat door._local_chat reads blank as "managed chat", which a client without an api_key does not have: chat_completions() reached for LocalAPI.chat_completions and raised a bare AttributeError. The managed branch now raises a PageIndexAPIError naming chat_model.

storage_path is typed str | os.PathLike[str]._ARG_TYPES accepts os.PathLike, so Path(...) ran fine, and py.typed made the Optional[str] annotation authoritative for callers' type checkers. Both signatures and LocalIndexConfig now agree with the runtime check.

Also on the branch: index= / chat= typed Mapping[str, Any] so the exported config shapes pass, plus a comment and two docstrings that stop overclaiming.

434 tests (432 + 2, both red on main). A review view of #424 + these follow-ups together is open against pre-424-main.

https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb

… a blank chat_model refuses at the chat door; storage_path is typed PathLike
find_dotenv(usecwd=True) returns '' when nothing is reachable from the
cwd, and `or None` turned that into load_dotenv's own upward walk from
utils.py — the install-dir leak the cwd search was added to replace. A
pip-installed SDK could load another project's .env from above
site-packages, silently.
_local_chat treats a blank chat_model as "managed chat", which a client
without an api_key does not have: chat_completions() then reached for
LocalAPI.chat_completions and raised a bare AttributeError. The managed
branch now refuses as a PageIndexAPIError naming chat_model.
py.typed made the annotations authoritative while storage_path was typed
str; _ARG_TYPES accepts os.PathLike, so Path(...) ran fine and failed the
user's type check. Both signatures and LocalIndexConfig now say so.
Claude-Session: https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb
… two docstrings stop overclaiming
The slots were annotated dict[str, Any]. A TypedDict is consistent with
Mapping[str, object], never with dict (PEP 589: a dict-typed receiver
could write arbitrary keys through it), so the four shapes types.py
exports — and py.typed advertises to installed callers' checkers —
could not be passed to the one place they describe. pyright on a probe
that does exactly that: 9 errors before, 0 after. The constructor only
reads the slot (items(), then a fresh conf dict), so Mapping is the
honest bound; a plain dict is a Mapping, and TypedDict instances are
plain dicts at runtime, so nothing moves at runtime.
The _ARG_TYPES comment said "every value" is shape-checked; api_key is
not in the table (its empty check is separate, its type check stays
unchecked by ruling), so the comment now speaks for the table only.
_local_doc_scope and _require_local_scope still explained the cloud
drop as "scoping is server-side" — true of the managed chat, which
never reaches either function. What reaches them on a cloud client is
own-model chat and the config helpers, whose cloud tools take no
allowlist: targeting there is prompt-level only, as the error message
between them already said.
434 passed; pyright on pageindex/ unchanged at 235 (0 in the touched
files, before and after).
Claude-Session: https://claude.ai/code/session_01TxG8u8x29XRnK4yscZVCch
@rejojer
rejojer merged commit b9a9a3b into mainAug 26, 2026
14 checks passed
rejojer added a commit that referenced this pull request Aug 26, 2026
Two post-0.2.11 follow-up squashes (b9a9a3b, 174f95f) land on the review
branch the same way f6fa99b did: main's tree recorded as merged, history
kept, so PR #400 keeps spanning 0.2.9 → current main.
Claude-Session: https://claude.ai/code/session_01VQ6mruXZBgw9Hjii8KPbQP
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike by rejojer · Pull Request #428 · VectifyAI/PageIndex · GitHub
Skip to content

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike - #428

Merged
rejojer merged 3 commits into
mainfrom
fix/client-config-followups
Aug 26, 2026
Merged

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike#428
rejojer merged 3 commits into
mainfrom
fix/client-config-followups

Conversation

@rejojer

Copy link
Copy Markdown
Member

Three follow-ups to #424, on the shipped v0.2.11.

.env search ends at the cwd tree.find_dotenv(usecwd=True) returns '' when nothing is reachable from the cwd, and or None turned that into load_dotenv's own upward walk from utils.py — the install-dir search the cwd search was added to replace. A pip-installed SDK could silently load another project's .env from above site-packages. Test plants a .env above a symlinked package and runs from a tree that has none.

A local client with a blank chat_model refuses at the chat door._local_chat reads blank as "managed chat", which a client without an api_key does not have: chat_completions() reached for LocalAPI.chat_completions and raised a bare AttributeError. The managed branch now raises a PageIndexAPIError naming chat_model.

storage_path is typed str | os.PathLike[str]._ARG_TYPES accepts os.PathLike, so Path(...) ran fine, and py.typed made the Optional[str] annotation authoritative for callers' type checkers. Both signatures and LocalIndexConfig now agree with the runtime check.

Also on the branch: index= / chat= typed Mapping[str, Any] so the exported config shapes pass, plus a comment and two docstrings that stop overclaiming.

434 tests (432 + 2, both red on main). A review view of #424 + these follow-ups together is open against pre-424-main.

https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb

… a blank chat_model refuses at the chat door; storage_path is typed PathLike
find_dotenv(usecwd=True) returns '' when nothing is reachable from the
cwd, and `or None` turned that into load_dotenv's own upward walk from
utils.py — the install-dir leak the cwd search was added to replace. A
pip-installed SDK could load another project's .env from above
site-packages, silently.
_local_chat treats a blank chat_model as "managed chat", which a client
without an api_key does not have: chat_completions() then reached for
LocalAPI.chat_completions and raised a bare AttributeError. The managed
branch now refuses as a PageIndexAPIError naming chat_model.
py.typed made the annotations authoritative while storage_path was typed
str; _ARG_TYPES accepts os.PathLike, so Path(...) ran fine and failed the
user's type check. Both signatures and LocalIndexConfig now say so.
Claude-Session: https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb
… two docstrings stop overclaiming
The slots were annotated dict[str, Any]. A TypedDict is consistent with
Mapping[str, object], never with dict (PEP 589: a dict-typed receiver
could write arbitrary keys through it), so the four shapes types.py
exports — and py.typed advertises to installed callers' checkers —
could not be passed to the one place they describe. pyright on a probe
that does exactly that: 9 errors before, 0 after. The constructor only
reads the slot (items(), then a fresh conf dict), so Mapping is the
honest bound; a plain dict is a Mapping, and TypedDict instances are
plain dicts at runtime, so nothing moves at runtime.
The _ARG_TYPES comment said "every value" is shape-checked; api_key is
not in the table (its empty check is separate, its type check stays
unchecked by ruling), so the comment now speaks for the table only.
_local_doc_scope and _require_local_scope still explained the cloud
drop as "scoping is server-side" — true of the managed chat, which
never reaches either function. What reaches them on a cloud client is
own-model chat and the config helpers, whose cloud tools take no
allowlist: targeting there is prompt-level only, as the error message
between them already said.
434 passed; pyright on pageindex/ unchanged at 235 (0 in the touched
files, before and after).
Claude-Session: https://claude.ai/code/session_01TxG8u8x29XRnK4yscZVCch
@rejojer
rejojer merged commit b9a9a3b into mainAug 26, 2026
14 checks passed
rejojer added a commit that referenced this pull request Aug 26, 2026
Two post-0.2.11 follow-up squashes (b9a9a3b, 174f95f) land on the review
branch the same way f6fa99b did: main's tree recorded as merged, history
kept, so PR #400 keeps spanning 0.2.9 → current main.
Claude-Session: https://claude.ai/code/session_01VQ6mruXZBgw9Hjii8KPbQP
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike by rejojer · Pull Request #428 · VectifyAI/PageIndex · GitHub
Skip to content

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike - #428

Merged
rejojer merged 3 commits into
mainfrom
fix/client-config-followups
Aug 26, 2026
Merged

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike#428
rejojer merged 3 commits into
mainfrom
fix/client-config-followups

Conversation

@rejojer

Copy link
Copy Markdown
Member

Three follow-ups to #424, on the shipped v0.2.11.

.env search ends at the cwd tree.find_dotenv(usecwd=True) returns '' when nothing is reachable from the cwd, and or None turned that into load_dotenv's own upward walk from utils.py — the install-dir search the cwd search was added to replace. A pip-installed SDK could silently load another project's .env from above site-packages. Test plants a .env above a symlinked package and runs from a tree that has none.

A local client with a blank chat_model refuses at the chat door._local_chat reads blank as "managed chat", which a client without an api_key does not have: chat_completions() reached for LocalAPI.chat_completions and raised a bare AttributeError. The managed branch now raises a PageIndexAPIError naming chat_model.

storage_path is typed str | os.PathLike[str]._ARG_TYPES accepts os.PathLike, so Path(...) ran fine, and py.typed made the Optional[str] annotation authoritative for callers' type checkers. Both signatures and LocalIndexConfig now agree with the runtime check.

Also on the branch: index= / chat= typed Mapping[str, Any] so the exported config shapes pass, plus a comment and two docstrings that stop overclaiming.

434 tests (432 + 2, both red on main). A review view of #424 + these follow-ups together is open against pre-424-main.

https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb

… a blank chat_model refuses at the chat door; storage_path is typed PathLike
find_dotenv(usecwd=True) returns '' when nothing is reachable from the
cwd, and `or None` turned that into load_dotenv's own upward walk from
utils.py — the install-dir leak the cwd search was added to replace. A
pip-installed SDK could load another project's .env from above
site-packages, silently.
_local_chat treats a blank chat_model as "managed chat", which a client
without an api_key does not have: chat_completions() then reached for
LocalAPI.chat_completions and raised a bare AttributeError. The managed
branch now refuses as a PageIndexAPIError naming chat_model.
py.typed made the annotations authoritative while storage_path was typed
str; _ARG_TYPES accepts os.PathLike, so Path(...) ran fine and failed the
user's type check. Both signatures and LocalIndexConfig now say so.
Claude-Session: https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb
… two docstrings stop overclaiming
The slots were annotated dict[str, Any]. A TypedDict is consistent with
Mapping[str, object], never with dict (PEP 589: a dict-typed receiver
could write arbitrary keys through it), so the four shapes types.py
exports — and py.typed advertises to installed callers' checkers —
could not be passed to the one place they describe. pyright on a probe
that does exactly that: 9 errors before, 0 after. The constructor only
reads the slot (items(), then a fresh conf dict), so Mapping is the
honest bound; a plain dict is a Mapping, and TypedDict instances are
plain dicts at runtime, so nothing moves at runtime.
The _ARG_TYPES comment said "every value" is shape-checked; api_key is
not in the table (its empty check is separate, its type check stays
unchecked by ruling), so the comment now speaks for the table only.
_local_doc_scope and _require_local_scope still explained the cloud
drop as "scoping is server-side" — true of the managed chat, which
never reaches either function. What reaches them on a cloud client is
own-model chat and the config helpers, whose cloud tools take no
allowlist: targeting there is prompt-level only, as the error message
between them already said.
434 passed; pyright on pageindex/ unchanged at 235 (0 in the touched
files, before and after).
Claude-Session: https://claude.ai/code/session_01TxG8u8x29XRnK4yscZVCch
@rejojer
rejojer merged commit b9a9a3b into mainAug 26, 2026
14 checks passed
rejojer added a commit that referenced this pull request Aug 26, 2026
Two post-0.2.11 follow-up squashes (b9a9a3b, 174f95f) land on the review
branch the same way f6fa99b did: main's tree recorded as merged, history
kept, so PR #400 keeps spanning 0.2.9 → current main.
Claude-Session: https://claude.ai/code/session_01VQ6mruXZBgw9Hjii8KPbQP
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike by rejojer · Pull Request #428 · VectifyAI/PageIndex · GitHub
Skip to content

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike - #428

Merged
rejojer merged 3 commits into
mainfrom
fix/client-config-followups
Aug 26, 2026
Merged

fix: the .env search ends at the cwd tree; a local client with a blank chat_model refuses at the chat door; storage_path is typed PathLike#428
rejojer merged 3 commits into
mainfrom
fix/client-config-followups

Conversation

@rejojer

Copy link
Copy Markdown
Member

Three follow-ups to #424, on the shipped v0.2.11.

.env search ends at the cwd tree.find_dotenv(usecwd=True) returns '' when nothing is reachable from the cwd, and or None turned that into load_dotenv's own upward walk from utils.py — the install-dir search the cwd search was added to replace. A pip-installed SDK could silently load another project's .env from above site-packages. Test plants a .env above a symlinked package and runs from a tree that has none.

A local client with a blank chat_model refuses at the chat door._local_chat reads blank as "managed chat", which a client without an api_key does not have: chat_completions() reached for LocalAPI.chat_completions and raised a bare AttributeError. The managed branch now raises a PageIndexAPIError naming chat_model.

storage_path is typed str | os.PathLike[str]._ARG_TYPES accepts os.PathLike, so Path(...) ran fine, and py.typed made the Optional[str] annotation authoritative for callers' type checkers. Both signatures and LocalIndexConfig now agree with the runtime check.

Also on the branch: index= / chat= typed Mapping[str, Any] so the exported config shapes pass, plus a comment and two docstrings that stop overclaiming.

434 tests (432 + 2, both red on main). A review view of #424 + these follow-ups together is open against pre-424-main.

https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb

… a blank chat_model refuses at the chat door; storage_path is typed PathLike
find_dotenv(usecwd=True) returns '' when nothing is reachable from the
cwd, and `or None` turned that into load_dotenv's own upward walk from
utils.py — the install-dir leak the cwd search was added to replace. A
pip-installed SDK could load another project's .env from above
site-packages, silently.
_local_chat treats a blank chat_model as "managed chat", which a client
without an api_key does not have: chat_completions() then reached for
LocalAPI.chat_completions and raised a bare AttributeError. The managed
branch now refuses as a PageIndexAPIError naming chat_model.
py.typed made the annotations authoritative while storage_path was typed
str; _ARG_TYPES accepts os.PathLike, so Path(...) ran fine and failed the
user's type check. Both signatures and LocalIndexConfig now say so.
Claude-Session: https://claude.ai/code/session_017Fd7jVm366S2Xamzhxv6yb
… two docstrings stop overclaiming
The slots were annotated dict[str, Any]. A TypedDict is consistent with
Mapping[str, object], never with dict (PEP 589: a dict-typed receiver
could write arbitrary keys through it), so the four shapes types.py
exports — and py.typed advertises to installed callers' checkers —
could not be passed to the one place they describe. pyright on a probe
that does exactly that: 9 errors before, 0 after. The constructor only
reads the slot (items(), then a fresh conf dict), so Mapping is the
honest bound; a plain dict is a Mapping, and TypedDict instances are
plain dicts at runtime, so nothing moves at runtime.
The _ARG_TYPES comment said "every value" is shape-checked; api_key is
not in the table (its empty check is separate, its type check stays
unchecked by ruling), so the comment now speaks for the table only.
_local_doc_scope and _require_local_scope still explained the cloud
drop as "scoping is server-side" — true of the managed chat, which
never reaches either function. What reaches them on a cloud client is
own-model chat and the config helpers, whose cloud tools take no
allowlist: targeting there is prompt-level only, as the error message
between them already said.
434 passed; pyright on pageindex/ unchanged at 235 (0 in the touched
files, before and after).
Claude-Session: https://claude.ai/code/session_01TxG8u8x29XRnK4yscZVCch
@rejojer
rejojer merged commit b9a9a3b into mainAug 26, 2026
14 checks passed
rejojer added a commit that referenced this pull request Aug 26, 2026
Two post-0.2.11 follow-up squashes (b9a9a3b, 174f95f) land on the review
branch the same way f6fa99b did: main's tree recorded as merged, history
kept, so PR #400 keeps spanning 0.2.9 → current main.
Claude-Session: https://claude.ai/code/session_01VQ6mruXZBgw9Hjii8KPbQP
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer