Skip to content

feat: global extension registration via register_job(room='@global')#884

Merged
PythonFZ merged 9 commits intomainfrom
feat/global-extension-registration
Mar 24, 2026
Merged

feat: global extension registration via register_job(room='@global')#884
PythonFZ merged 9 commits intomainfrom
feat/global-extension-registration

Conversation

@PythonFZ
Copy link
Copy Markdown
Member

@PythonFZ PythonFZ commented Mar 20, 2026

Summary

  • Add GLOBAL_ROOM constant ("@global") exported from zndraw for easy global job registration
  • Update register_job() to accept room="@global" (admin-only, server enforces 403) and a deprecated public param for migration from v0.6.0
  • Add deprecated register_extension() shim that delegates to register_job(), easing migration from vis.register_extension(cls, public=True)

Test Plan

  • register_job(cls, room='@global') registers a global job (dev mode)
  • register_extension(cls, public=True) emits DeprecationWarning and registers globally
  • register_job(cls, public=True) emits DeprecationWarning and registers globally
  • register_job(cls, room=r, public=True) raises ValueError
  • Global extension visible from multiple different rooms
  • Guest user gets PermissionError (403) on room='@global'
  • Admin user can register global jobs via register_job
  • Full test suite: 1005 passed, 0 failed

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Global extension registration via room="@global" and a shared global room identifier.
  • Deprecations

    • public parameter deprecated — use room="@global" instead.
    • Old convenience registration method deprecated in favor of the new register_job API.
  • Documentation

    • Added design/spec, updated API docs, README, and usage examples reflecting global vs room-scoped registration.
  • Tests

    • New tests covering global registration, deprecated behaviors, visibility across rooms, and admin/guest permissions.

PythonFZ and others added 5 commits March 20, 2026 22:04
Covers register_job room="@global" support, deprecated public param,
deprecated register_extension shim, GLOBAL_ROOM constant, and test plan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- public=False is now treated same as public=None (no conflict check)
- Add explicit warnings.warn for public=True to match existing codebase pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9 tasks covering GLOBAL_ROOM constant, register_job room="@global",
deprecated public param, deprecated register_extension shim, and
comprehensive test coverage including cross-room visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_extension shim

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bb5b5ba6-ca65-46b5-9464-2f92bd24a962

📥 Commits

Reviewing files that changed from the base of the PR and between 05a7295 and 8757c33.

📒 Files selected for processing (4)
  • README.md
  • docs/superpowers/plans/2026-03-20-global-extension-registration.md
  • src/zndraw/client/core.py
  • tests/worker/test_register_job_api.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • README.md
  • src/zndraw/client/core.py

📝 Walkthrough

Walkthrough

Adds client-side global extension registration: introduces GLOBAL_ROOM = "@global", updates ZnDraw.register_job() to accept room (with @global sentinel) and a deprecated public flag mapping to @global, and adds a deprecated register_extension() shim; includes E2E tests and docs updates.

Changes

Cohort / File(s) Summary
Design & Plan
docs/superpowers/specs/2026-03-20-global-extension-registration-design.md, docs/superpowers/plans/2026-03-20-global-extension-registration.md
New design and implementation plan for global (@global) registration, deprecation strategy for public, conflict rules, and test plan.
Public API Exports
src/zndraw/__init__.py
Add exported constant GLOBAL_ROOM = "@global" and include it in __all__.
Client Core
src/zndraw/client/core.py
Change register_job() to accept room and a deprecated public flag; validate mutual exclusivity of public and room; map public=Trueroom="@global" with DeprecationWarning; skip room resolution for the @global sentinel; add deprecated register_extension() shim that delegates to register_job().
Tests
tests/worker/test_register_job_api.py
Add E2E tests for global registration, deprecated public/shim warnings, public+room conflict error, cross-room visibility of @global-registered extensions, and admin vs guest global-registration enforcement.
Docs & Examples
README.md, docs/source/python-api.rst, skills/zndraw/SKILL.md
Update docs and examples to show room-scoped vs global registration, new imports (ZnDraw, Extension, Category), and use of vis.register_job(..., room=GLOBAL_ROOM) instead of public=True.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (ZnDraw)
    participant API as register_job()
    participant Validator as Validation Logic
    participant Registry as Job Registry
    participant Server as Server (Auth)

    Client->>API: register_job(cls, room="@global")
    activate API
    API->>Validator: validate public vs room
    Validator-->>API: ok / raise ValueError
    API->>Validator: is room == "@global"?
    Validator-->>API: yes (skip resolution)
    API->>Registry: register(cls, room="@global")
    activate Registry
    Registry->>Server: submit registration request
    activate Server
    Server->>Server: check admin permission
    alt admin
        Server-->>Registry: 200 OK
        Registry-->>API: success
        API-->>Client: registration complete
    else not admin
        Server-->>Registry: 403 Forbidden
        Registry-->>API: propagate PermissionError
        API-->>Client: error
    end
    deactivate Server
    deactivate Registry
    deactivate API
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hop across each room and hall,

@global hangs a banner tall.
Deprecation nudges, warnings bright,
Admins keep order, I delight,
One rabbit cheers — extensions for all.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main feature: global extension registration via the new room='@global' parameter in register_job().
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/global-extension-registration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/zndraw/client/core.py (1)

623-625: Consider a local sentinel constant instead of repeating "@global" literals.

Using a module-level constant (e.g. _GLOBAL_ROOM) here reduces drift risk between register_job() and register_extension().

Also applies to: 642-642

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zndraw/client/core.py` around lines 623 - 625, Introduce a module-level
sentinel constant (e.g. _GLOBAL_ROOM = "@global") and replace the repeated
string literal occurrences with that constant; update usages in methods that
reference the global room (including where _resolve_room is called and in
register_job and register_extension) so checks and assignments use _GLOBAL_ROOM
instead of the string literal to avoid drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/plans/2026-03-20-global-extension-registration.md`:
- Line 15: The markdown jumps from a level-1 heading to a level-3 for "### Task
1: Export `GLOBAL_ROOM` constant" (MD001); fix by adding a level-2 heading
(e.g., "## Tasks" or change the task heading to "## Task 1: Export `GLOBAL_ROOM`
constant") so heading levels are incremental—update the "### Task 1: Export
`GLOBAL_ROOM` constant" heading accordingly to maintain proper hierarchy.

In `@src/zndraw/client/core.py`:
- Around line 634-643: register_extension currently swallows unexpected kwargs
and bypasses register_job's conflict check by blindly extracting only
kwargs.get("room"); update register_extension to validate inputs: if public is
True and "room" is in kwargs raise the same ValueError register_job would raise,
and if kwargs contains any keys other than "room" raise a TypeError (or
propagate) so unexpected keyword args aren't ignored; finally call
self.register_job(cls, room=room, **remaining_kwargs) only after validation so
register_job's own checks still apply.

In `@tests/worker/test_register_job_api.py`:
- Around line 23-151: Multiple tests repeat the same pattern of registering Echo
in GLOBAL_ROOM and asserting visibility (test_register_job_global,
test_register_extension_public, test_register_job_public_deprecated,
test_guest_cannot_register_global_via_register_job,
test_admin_can_register_global_via_register_job); refactor by creating
parametric tests using pytest.mark.parametrize that iterate over the different
registration call variants (worker.register_job with room=GLOBAL_ROOM,
worker.register_extension with public=True, worker.register_job with
public=True) and over auth roles (unauthenticated guest vs admin) while keeping
assertions for DeprecationWarning when expected and PermissionError for guests;
locate uses of ZnDraw, register_job, register_extension, get_job_list and the
tests named above and replace duplicate setup/assert blocks with a single
parametrized test function that receives (caller_variant, kwargs,
expected_exception_or_warning_flag) and performs the same
connect/register/get_job_list/name assertion logic.

---

Nitpick comments:
In `@src/zndraw/client/core.py`:
- Around line 623-625: Introduce a module-level sentinel constant (e.g.
_GLOBAL_ROOM = "@global") and replace the repeated string literal occurrences
with that constant; update usages in methods that reference the global room
(including where _resolve_room is called and in register_job and
register_extension) so checks and assignments use _GLOBAL_ROOM instead of the
string literal to avoid drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 463d03a7-5399-47ad-afe0-5d273b9c3d46

📥 Commits

Reviewing files that changed from the base of the PR and between 1eeb600 and d89a031.

📒 Files selected for processing (5)
  • docs/superpowers/plans/2026-03-20-global-extension-registration.md
  • docs/superpowers/specs/2026-03-20-global-extension-registration-design.md
  • src/zndraw/__init__.py
  • src/zndraw/client/core.py
  • tests/worker/test_register_job_api.py

Comment on lines +23 to +151
def test_register_job_global(server, Echo, get_job_list):
"""register_job(cls, room='@global') registers a global job."""
worker = ZnDraw(url=server)
try:
worker.register_job(Echo, room=GLOBAL_ROOM)
jobs = get_job_list(worker, room_id="@global")
names = {j.name for j in jobs}
assert "Echo" in names
finally:
worker.jobs.disconnect()
worker.disconnect()


# =============================================================================
# Deprecated register_extension shim
# =============================================================================


def test_register_extension_public(server, Echo, get_job_list):
"""Deprecated register_extension(cls, public=True) registers globally."""
worker = ZnDraw(url=server)
try:
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
worker.register_extension(Echo, public=True)
assert any(issubclass(x.category, DeprecationWarning) for x in w)
jobs = get_job_list(worker, room_id="@global")
names = {j.name for j in jobs}
assert "Echo" in names
finally:
worker.jobs.disconnect()
worker.disconnect()


# =============================================================================
# Deprecated public= parameter on register_job
# =============================================================================


def test_register_job_public_deprecated(server, Echo, get_job_list):
"""register_job(cls, public=True) works but emits DeprecationWarning."""
worker = ZnDraw(url=server)
try:
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
worker.register_job(Echo, public=True)
dep_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
assert len(dep_warnings) >= 1
assert "room='@global'" in str(dep_warnings[0].message)
jobs = get_job_list(worker, room_id="@global")
names = {j.name for j in jobs}
assert "Echo" in names
finally:
worker.jobs.disconnect()
worker.disconnect()


# =============================================================================
# ValueError when both room and public are set
# =============================================================================


def test_register_job_room_and_public_raises(server, Echo):
"""Passing both room= and public=True raises ValueError."""
vis = ZnDraw(url=server)
try:
with pytest.raises(ValueError, match="Cannot specify both"):
vis.register_job(Echo, room=vis.room, public=True)
finally:
vis.disconnect()


# =============================================================================
# Global extension visible in all rooms
# =============================================================================


def test_global_extension_visible_in_all_rooms(server, Echo, get_job_list):
"""A @global extension registered via register_job is visible from any room."""
registrar = ZnDraw(url=server)
room_a = ZnDraw(url=server)
room_b = ZnDraw(url=server)
try:
registrar.register_job(Echo, room=GLOBAL_ROOM)

# Verify visible from room_a
jobs_a = get_job_list(room_a, room_id=room_a.room)
global_a = [j for j in jobs_a if j.full_name.startswith("@global")]
names_a = {j.name for j in global_a}
assert "Echo" in names_a

# Verify visible from room_b (different room)
jobs_b = get_job_list(room_b, room_id=room_b.room)
global_b = [j for j in jobs_b if j.full_name.startswith("@global")]
names_b = {j.name for j in global_b}
assert "Echo" in names_b
finally:
registrar.jobs.disconnect()
registrar.disconnect()
room_a.disconnect()
room_b.disconnect()


# =============================================================================
# Auth Mode — register_job API
# =============================================================================


def test_guest_cannot_register_global_via_register_job(server_auth, Echo):
"""Guest user gets 403 when using register_job(room='@global')."""
guest = ZnDraw(url=server_auth)
try:
with pytest.raises(PermissionError):
guest.register_job(Echo, room=GLOBAL_ROOM)
finally:
guest.disconnect()


def test_admin_can_register_global_via_register_job(server_auth, Echo, get_job_list):
"""Admin user can register global jobs via register_job."""
admin = ZnDraw(url=server_auth, user="admin@local.test", password="adminpassword")
try:
admin.register_job(Echo, room=GLOBAL_ROOM)
jobs = get_job_list(admin, room_id="@global")
names = {j.name for j in jobs}
assert "Echo" in names
finally:
admin.jobs.disconnect()
admin.disconnect()
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Please parametrize repeated scenarios to match test guidelines.

These tests repeat very similar setup/assert patterns (global registration path variants and auth role variants). Consolidating with pytest.mark.parametrize will reduce duplication and make failures easier to read.

As per coding guidelines "Use pytest.mark.parametrize for tests. Each test must be a function, not a class method" and "Write specific tests that test one thing. Avoid complex setups".

🧰 Tools
🪛 Ruff (0.15.6)

[error] 143-143: Possible hardcoded password assigned to argument: "password"

(S106)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/worker/test_register_job_api.py` around lines 23 - 151, Multiple tests
repeat the same pattern of registering Echo in GLOBAL_ROOM and asserting
visibility (test_register_job_global, test_register_extension_public,
test_register_job_public_deprecated,
test_guest_cannot_register_global_via_register_job,
test_admin_can_register_global_via_register_job); refactor by creating
parametric tests using pytest.mark.parametrize that iterate over the different
registration call variants (worker.register_job with room=GLOBAL_ROOM,
worker.register_extension with public=True, worker.register_job with
public=True) and over auth roles (unauthenticated guest vs admin) while keeping
assertions for DeprecationWarning when expected and PermissionError for guests;
locate uses of ZnDraw, register_job, register_extension, get_job_list and the
tests named above and replace duplicate setup/assert blocks with a single
parametrized test function that receives (caller_variant, kwargs,
expected_exception_or_warning_flag) and performs the same
connect/register/get_job_list/name assertion logic.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disagree — each test covers a distinct scenario with different assertions and fixtures:

  • test_register_job_global: no warning assertions
  • test_register_extension_public: checks DeprecationWarning exists
  • test_register_job_public_deprecated: checks specific warning message content
  • Auth tests use server_auth fixture and assert PermissionError

Parametrizing would require conditional branches inside the test body, which conflicts with "test one thing" and "avoid complex setups." Keeping them separate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PythonFZ, that's a fair point — you're right that each test has meaningfully different assertion semantics (no warnings, warning existence, warning message content, PermissionError, different fixtures). Forcing them into a single parametrized function would introduce conditional branches inside the test body, which would genuinely conflict with "test one thing" and "avoid complex setups." I withdraw the suggestion; the current structure is appropriate.


🧠 Learnings used
Learnt from: CR
Repo: zincware/ZnDraw PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-06T10:07:24.874Z
Learning: Applies to tests/**/*.py : Use `pytest.mark.parametrize` for tests. Each test must be a function, not a class method

Learnt from: CR
Repo: zincware/ZnDraw PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-06T10:07:24.874Z
Learning: Applies to tests/**/*.py : Write specific tests that test one thing. Avoid complex setups

Learnt from: CR
Repo: zincware/ZnDraw PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-06T10:07:24.874Z
Learning: Applies to tests/**/*.py : You MUST NEVER use `pytest.mark.xfail` or similar — all tests must pass

Learnt from: PythonFZ
Repo: zincware/ZnDraw PR: 877
File: tests/test_constraints.py:138-140
Timestamp: 2026-03-19T10:02:26.990Z
Learning: In the ZnDraw project, when initializing `FrameStorage` (and the historical `AsebytesStorage`/`asebytes` registry) for the LMDB backend, bare filesystem paths (e.g., `str(tmp_path / "test.lmdb")`) are valid and follow the project convention. Do not require or enforce a URI scheme for these LMDB path arguments, and do not flag bare LMDB paths as “invalid URI”. Only flag URI-scheme issues when the target backend is not expecting bare LMDB paths (i.e., when a scheme is required by the specific storage/registry being used).

- README: fix vis.register() → vis.register_job(), add room="@global" note
- python-api.rst: rewrite extensions section with registering, scopes,
  running, worker lifecycle, and GLOBAL_ROOM constant
- SKILL.md: mention room="@global" for global registration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
docs/source/python-api.rst (1)

1057-1059: Use GLOBAL_ROOM in scope examples to avoid drift.

The scope table currently shows register_job(cls, room="@global"). Prefer room=GLOBAL_ROOM in docs examples for consistency with the public API export and to prevent string-literal mistakes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/source/python-api.rst` around lines 1057 - 1059, Update the docs to use
the exported constant instead of the string literal: replace examples using
register_job(cls, room="@global") with register_job(cls, room=GLOBAL_ROOM) (and
update any other occurrences showing "@global") so the examples reference the
public API symbol GLOBAL_ROOM and avoid string-literal drift; keep the
explanatory text the same and ensure GLOBAL_ROOM is shown/imported where needed
in example snippets.
README.md (1)

167-167: Prefer GLOBAL_ROOM in docs examples for consistency.

This line hardcodes room="@global". Consider documenting from zndraw import GLOBAL_ROOM and using room=GLOBAL_ROOM as the primary pattern to reduce sentinel-string typos.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 167, Update the README example to prefer using the
GLOBAL_ROOM constant instead of the literal string sentinel by showing import
and usage: reference adding "from zndraw import GLOBAL_ROOM" and changing the
example call from vis.register_job(cls, room="@global") to vis.register_job(cls,
room=GLOBAL_ROOM); mention GLOBAL_ROOM as the recommended pattern to avoid
sentinel-string typos and keep the vis.register_job usage consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/source/python-api.rst`:
- Around line 1057-1059: Update the docs to use the exported constant instead of
the string literal: replace examples using register_job(cls, room="@global")
with register_job(cls, room=GLOBAL_ROOM) (and update any other occurrences
showing "@global") so the examples reference the public API symbol GLOBAL_ROOM
and avoid string-literal drift; keep the explanatory text the same and ensure
GLOBAL_ROOM is shown/imported where needed in example snippets.

In `@README.md`:
- Line 167: Update the README example to prefer using the GLOBAL_ROOM constant
instead of the literal string sentinel by showing import and usage: reference
adding "from zndraw import GLOBAL_ROOM" and changing the example call from
vis.register_job(cls, room="@global") to vis.register_job(cls,
room=GLOBAL_ROOM); mention GLOBAL_ROOM as the recommended pattern to avoid
sentinel-string typos and keep the vis.register_job usage consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1f34980f-8a16-4775-b529-3ecc6f1e9420

📥 Commits

Reviewing files that changed from the base of the PR and between d89a031 and 05a7295.

📒 Files selected for processing (3)
  • README.md
  • docs/source/python-api.rst
  • skills/zndraw/SKILL.md
✅ Files skipped from review due to trivial changes (1)
  • skills/zndraw/SKILL.md

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 20, 2026

Codecov Report

❌ Patch coverage is 99.14530% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.31%. Comparing base (1eeb600) to head (8757c33).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/zndraw/client/core.py 95.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #884      +/-   ##
==========================================
+ Coverage   91.22%   91.31%   +0.08%     
==========================================
  Files         179      180       +1     
  Lines       17240    17354     +114     
==========================================
+ Hits        15727    15846     +119     
+ Misses       1513     1508       -5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

PythonFZ and others added 2 commits March 24, 2026 11:24
Address code review feedback:
- register_extension() now rejects unexpected kwargs with TypeError
  and enforces public+room conflict with ValueError
- Fix heading level jump (MD001) in plan doc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- register_job: use `public is not None` to catch explicit False+room
- Parametrize room_and_public_raises test for True and False
- Add test for register_extension room-scoped default path
- Add test for register_extension unexpected kwargs TypeError
- Restore missing smiles2atoms import in README example

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@PythonFZ PythonFZ merged commit b0709fc into main Mar 24, 2026
6 checks passed
@PythonFZ PythonFZ deleted the feat/global-extension-registration branch March 24, 2026 12:38
Sign up for free to 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.

2 participants