django-huddle 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. django_huddle-0.1.0/.editorconfig +37 -0
  2. django_huddle-0.1.0/.github/dependabot.yml +24 -0
  3. django_huddle-0.1.0/.github/pull_request_template.md +17 -0
  4. django_huddle-0.1.0/.github/workflows/ci.yml +120 -0
  5. django_huddle-0.1.0/.github/workflows/codeql.yml +34 -0
  6. django_huddle-0.1.0/.github/workflows/publish.yml +62 -0
  7. django_huddle-0.1.0/.github/workflows/zizmor.yml +36 -0
  8. django_huddle-0.1.0/.gitignore +63 -0
  9. django_huddle-0.1.0/.pre-commit-config.yaml +45 -0
  10. django_huddle-0.1.0/AGENTS.md +44 -0
  11. django_huddle-0.1.0/CHANGELOG.md +12 -0
  12. django_huddle-0.1.0/LICENSE +21 -0
  13. django_huddle-0.1.0/PKG-INFO +278 -0
  14. django_huddle-0.1.0/README.md +247 -0
  15. django_huddle-0.1.0/docs/adr/0001-build-chat-on-channels.md +48 -0
  16. django_huddle-0.1.0/docs/adr/0002-single-chat-policy.md +36 -0
  17. django_huddle-0.1.0/docs/adr/0003-ids-not-html-over-the-channel-layer.md +32 -0
  18. django_huddle-0.1.0/docs/adr/0004-token-message-format.md +28 -0
  19. django_huddle-0.1.0/docs/adr/0005-overridable-components-and-api.md +35 -0
  20. django_huddle-0.1.0/docs/adr/0006-django-6-floor.md +19 -0
  21. django_huddle-0.1.0/docs/adr/index.md +14 -0
  22. django_huddle-0.1.0/example/__init__.py +0 -0
  23. django_huddle-0.1.0/example/asgi.py +22 -0
  24. django_huddle-0.1.0/example/settings.py +64 -0
  25. django_huddle-0.1.0/example/templates/example_base.html +13 -0
  26. django_huddle-0.1.0/example/urls.py +11 -0
  27. django_huddle-0.1.0/example/wsgi.py +8 -0
  28. django_huddle-0.1.0/justfile +28 -0
  29. django_huddle-0.1.0/manage.py +11 -0
  30. django_huddle-0.1.0/pyproject.toml +143 -0
  31. django_huddle-0.1.0/src/huddle/__init__.py +6 -0
  32. django_huddle-0.1.0/src/huddle/api/__init__.py +4 -0
  33. django_huddle-0.1.0/src/huddle/api/serializers.py +50 -0
  34. django_huddle-0.1.0/src/huddle/api/urls.py +16 -0
  35. django_huddle-0.1.0/src/huddle/api/views.py +255 -0
  36. django_huddle-0.1.0/src/huddle/apps.py +17 -0
  37. django_huddle-0.1.0/src/huddle/conf.py +54 -0
  38. django_huddle-0.1.0/src/huddle/consumers.py +179 -0
  39. django_huddle-0.1.0/src/huddle/exceptions.py +54 -0
  40. django_huddle-0.1.0/src/huddle/forms.py +89 -0
  41. django_huddle-0.1.0/src/huddle/markup.py +93 -0
  42. django_huddle-0.1.0/src/huddle/migrations/0001_initial.py +112 -0
  43. django_huddle-0.1.0/src/huddle/migrations/__init__.py +0 -0
  44. django_huddle-0.1.0/src/huddle/models.py +194 -0
  45. django_huddle-0.1.0/src/huddle/payloads.py +87 -0
  46. django_huddle-0.1.0/src/huddle/policy.py +121 -0
  47. django_huddle-0.1.0/src/huddle/py.typed +0 -0
  48. django_huddle-0.1.0/src/huddle/querysets.py +137 -0
  49. django_huddle-0.1.0/src/huddle/realtime.py +58 -0
  50. django_huddle-0.1.0/src/huddle/rendering.py +117 -0
  51. django_huddle-0.1.0/src/huddle/routing.py +11 -0
  52. django_huddle-0.1.0/src/huddle/services.py +248 -0
  53. django_huddle-0.1.0/src/huddle/signals.py +17 -0
  54. django_huddle-0.1.0/src/huddle/static/huddle/huddle.css +254 -0
  55. django_huddle-0.1.0/src/huddle/static/huddle/huddle.js +134 -0
  56. django_huddle-0.1.0/src/huddle/templates/huddle/base.html +13 -0
  57. django_huddle-0.1.0/src/huddle/templates/huddle/browse_page.html +2 -0
  58. django_huddle-0.1.0/src/huddle/templates/huddle/channel_page.html +2 -0
  59. django_huddle-0.1.0/src/huddle/templates/huddle/components/browse_list.html +19 -0
  60. django_huddle-0.1.0/src/huddle/templates/huddle/components/channel_pane.html +19 -0
  61. django_huddle-0.1.0/src/huddle/templates/huddle/components/composer.html +8 -0
  62. django_huddle-0.1.0/src/huddle/templates/huddle/components/composer_fields.html +6 -0
  63. django_huddle-0.1.0/src/huddle/templates/huddle/components/edit_form.html +7 -0
  64. django_huddle-0.1.0/src/huddle/templates/huddle/components/mention_results.html +6 -0
  65. django_huddle-0.1.0/src/huddle/templates/huddle/components/message.html +27 -0
  66. django_huddle-0.1.0/src/huddle/templates/huddle/components/posted.html +2 -0
  67. django_huddle-0.1.0/src/huddle/templates/huddle/components/replied.html +2 -0
  68. django_huddle-0.1.0/src/huddle/templates/huddle/components/reply_composer.html +8 -0
  69. django_huddle-0.1.0/src/huddle/templates/huddle/components/sidebar.html +22 -0
  70. django_huddle-0.1.0/src/huddle/templates/huddle/components/sidebar_entry.html +8 -0
  71. django_huddle-0.1.0/src/huddle/templates/huddle/components/thread.html +12 -0
  72. django_huddle-0.1.0/src/huddle/templates/huddle/components/thread_summary.html +8 -0
  73. django_huddle-0.1.0/src/huddle/templates/huddle/components/timeline_page.html +9 -0
  74. django_huddle-0.1.0/src/huddle/templates/huddle/components/ws_message.html +8 -0
  75. django_huddle-0.1.0/src/huddle/templates/huddle/components/ws_sidebar_entry.html +1 -0
  76. django_huddle-0.1.0/src/huddle/templates/huddle/create_channel_page.html +12 -0
  77. django_huddle-0.1.0/src/huddle/templates/huddle/home_page.html +12 -0
  78. django_huddle-0.1.0/src/huddle/templates/huddle/layout.html +11 -0
  79. django_huddle-0.1.0/src/huddle/templates/huddle/new_direct_page.html +12 -0
  80. django_huddle-0.1.0/src/huddle/urls.py +27 -0
  81. django_huddle-0.1.0/src/huddle/views.py +419 -0
  82. django_huddle-0.1.0/tests/__init__.py +0 -0
  83. django_huddle-0.1.0/tests/conftest.py +76 -0
  84. django_huddle-0.1.0/tests/settings.py +8 -0
  85. django_huddle-0.1.0/tests/test_api.py +123 -0
  86. django_huddle-0.1.0/tests/test_consumers.py +129 -0
  87. django_huddle-0.1.0/tests/test_markup_and_policy.py +114 -0
  88. django_huddle-0.1.0/tests/test_services.py +202 -0
  89. django_huddle-0.1.0/tests/test_views.py +192 -0
  90. django_huddle-0.1.0/tox.ini +21 -0
  91. django_huddle-0.1.0/uv.lock +1481 -0
@@ -0,0 +1,37 @@
1
+ # https://editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ charset = utf-8
6
+ end_of_line = lf
7
+ indent_style = space
8
+ indent_size = 4
9
+ trim_trailing_whitespace = true
10
+ insert_final_newline = true
11
+
12
+ [*.{html,css,js,json,sh,yml,yaml}]
13
+ indent_size = 2
14
+
15
+ [*.bat]
16
+ indent_style = tab
17
+ end_of_line = crlf
18
+
19
+ [LICENSE]
20
+ insert_final_newline = false
21
+
22
+ [justfile]
23
+ indent_style = space
24
+ indent_size = 4
25
+
26
+ # Ignore binary or generated files
27
+ [*.{png,jpg,gif,ico,woff,woff2,ttf,eot,svg,pdf}]
28
+ charset = unset
29
+ end_of_line = unset
30
+ indent_style = unset
31
+ indent_size = unset
32
+ trim_trailing_whitespace = unset
33
+ insert_final_newline = unset
34
+ max_line_length = unset
35
+
36
+ [*.{diff,patch}]
37
+ trim_trailing_whitespace = false
@@ -0,0 +1,24 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ time: "07:00"
9
+ timezone: Europe/London
10
+ cooldown:
11
+ default-days: 7
12
+ open-pull-requests-limit: 5
13
+
14
+ # Cooldown matches exclude-newer in pyproject.toml, so uv can always lock it.
15
+ - package-ecosystem: uv
16
+ directory: /
17
+ schedule:
18
+ interval: weekly
19
+ day: monday
20
+ time: "07:00"
21
+ timezone: Europe/London
22
+ cooldown:
23
+ default-days: 7
24
+ open-pull-requests-limit: 5
@@ -0,0 +1,17 @@
1
+ ## What
2
+
3
+ <!-- 1-3 sentences. Link the issue if there is one. -->
4
+
5
+ ## AI Provenance
6
+
7
+ <!-- Remove this section for human-authored PRs. -->
8
+
9
+ - **Tool:** <!-- Claude Code, Cursor, Copilot, Codex, etc. -->
10
+ - **Model:** <!-- Claude Opus 4.6, GPT-4o, etc. -->
11
+ - **Prompt/thread:** <!-- Paste your prompt or a link to the thread. -->
12
+
13
+ ## Checklist
14
+
15
+ - [ ] Addresses exactly one issue or feature
16
+ - [ ] New or changed behavior has test coverage
17
+ - [ ] Diff contains only changes for this task
@@ -0,0 +1,120 @@
1
+ name: CI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+ types:
10
+ - opened
11
+ - synchronize
12
+ - reopened
13
+ - ready_for_review
14
+
15
+ permissions: {}
16
+
17
+ concurrency:
18
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
19
+ cancel-in-progress: true
20
+
21
+ jobs:
22
+ lint:
23
+ name: Lint
24
+ runs-on: ubuntu-latest
25
+ permissions:
26
+ contents: read
27
+ steps:
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
+ with:
30
+ persist-credentials: false
31
+
32
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
33
+
34
+ - name: Ruff format check
35
+ run: uv run ruff format --check .
36
+
37
+ - name: Ruff lint
38
+ run: uv run ruff check .
39
+
40
+ type-check:
41
+ name: Type check
42
+ runs-on: ubuntu-latest
43
+ permissions:
44
+ contents: read
45
+ steps:
46
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
47
+ with:
48
+ persist-credentials: false
49
+
50
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
51
+
52
+ - name: Type check with mypy
53
+ run: uv run mypy src/ tests/ example/
54
+
55
+ test:
56
+ name: Test (Python ${{ matrix.python-version }})
57
+ runs-on: ubuntu-latest
58
+ strategy:
59
+ fail-fast: false
60
+ matrix:
61
+ python-version: ["3.12", "3.13", "3.14"]
62
+ permissions:
63
+ contents: read
64
+ steps:
65
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
66
+ with:
67
+ persist-credentials: false
68
+
69
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
70
+
71
+ - name: Run tox envs for Python ${{ matrix.python-version }}
72
+ run: uvx --with tox-uv tox run -f py$(echo ${{ matrix.python-version }} | tr -d .)
73
+
74
+ - name: Upload coverage data
75
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
76
+ with:
77
+ name: coverage-${{ matrix.python-version }}
78
+ path: .coverage.*
79
+ include-hidden-files: true
80
+ if-no-files-found: error
81
+
82
+ coverage:
83
+ name: Coverage
84
+ needs: [test]
85
+ runs-on: ubuntu-latest
86
+ permissions:
87
+ contents: read
88
+ steps:
89
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
90
+ with:
91
+ persist-credentials: false
92
+
93
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
94
+
95
+ - name: Download coverage data
96
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
97
+ with:
98
+ pattern: coverage-*
99
+ merge-multiple: true
100
+
101
+ - name: Combine coverage
102
+ run: uv run coverage combine
103
+
104
+ - name: Coverage report
105
+ run: |
106
+ echo '## Coverage Report' >> $GITHUB_STEP_SUMMARY
107
+ echo '```' >> $GITHUB_STEP_SUMMARY
108
+ uv run coverage report | tee -a $GITHUB_STEP_SUMMARY
109
+ echo '```' >> $GITHUB_STEP_SUMMARY
110
+
111
+ all-checks-pass:
112
+ name: All checks pass
113
+ if: always()
114
+ needs: [lint, type-check, test, coverage]
115
+ runs-on: ubuntu-latest
116
+ steps:
117
+ - name: Check job results
118
+ uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0
119
+ with:
120
+ jobs: ${{ toJSON(needs) }}
@@ -0,0 +1,34 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ - cron: '0 6 * * 1'
10
+
11
+ permissions: {}
12
+
13
+ jobs:
14
+ analyze:
15
+ name: Analyze
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: read
19
+ packages: read
20
+ security-events: write
21
+ steps:
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+
26
+ - name: Initialize CodeQL
27
+ uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
28
+ with:
29
+ languages: python # Add c-cpp, go, java-kotlin, or swift if needed
30
+ build-mode: none # Switch to autobuild for compiled languages
31
+ queries: security-extended
32
+
33
+ - name: Perform CodeQL Analysis
34
+ uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
@@ -0,0 +1,62 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions: {}
9
+
10
+ concurrency:
11
+ group: publish
12
+
13
+ jobs:
14
+ build:
15
+ name: Build distribution
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: read
19
+ steps:
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
+ with:
22
+ persist-credentials: false
23
+
24
+ # Cache disabled to prevent cache-poisoning attacks on release artifacts.
25
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
26
+ with:
27
+ enable-cache: false
28
+
29
+ - name: Build
30
+ run: uv build
31
+
32
+ - name: Upload distribution
33
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
34
+ with:
35
+ name: dist
36
+ path: dist/
37
+
38
+ publish:
39
+ name: Publish to PyPI
40
+ needs: build
41
+ runs-on: ubuntu-latest
42
+ permissions:
43
+ id-token: write
44
+ attestations: write
45
+ # Needs Settings > Environments > pypi: reviewers + v* tag restriction.
46
+ environment:
47
+ name: pypi
48
+ url: https://pypi.org/p/django-huddle
49
+ steps:
50
+ - name: Download distribution
51
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+
56
+ - name: Attest build provenance
57
+ uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
58
+ with:
59
+ subject-path: "dist/*"
60
+
61
+ - name: Publish to PyPI
62
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,36 @@
1
+ name: Workflow security analysis
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - ".github/workflows/**"
9
+ pull_request:
10
+ types:
11
+ - opened
12
+ - synchronize
13
+ - reopened
14
+ - ready_for_review
15
+ paths:
16
+ - ".github/workflows/**"
17
+
18
+ permissions: {}
19
+
20
+ jobs:
21
+ zizmor:
22
+ name: Run zizmor
23
+ runs-on: ubuntu-latest
24
+ permissions:
25
+ contents: read
26
+ actions: read
27
+ security-events: write
28
+ steps:
29
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30
+ with:
31
+ persist-credentials: false
32
+
33
+ - name: Run zizmor
34
+ uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3
35
+ with:
36
+ inputs: ./.github/
@@ -0,0 +1,63 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ build/
11
+ dist/
12
+ eggs/
13
+ .eggs/
14
+ *.egg-info/
15
+ *.egg
16
+ MANIFEST
17
+
18
+ # Unit test / coverage reports
19
+ htmlcov/
20
+ .tox/
21
+ .nox/
22
+ .coverage
23
+ .coverage.*
24
+ coverage.xml
25
+ .hypothesis/
26
+ .pytest_cache/
27
+
28
+ # Environments
29
+ .env
30
+ .envrc
31
+ .venv
32
+
33
+ # uv
34
+ # Generally recommended to include uv.lock in version control for
35
+ # reproducibility. Uncomment for libraries where you want consumers
36
+ # to resolve their own dependencies.
37
+ # uv.lock
38
+
39
+ # Ruff
40
+ .ruff_cache/
41
+
42
+ # Type checkers
43
+ .mypy_cache/
44
+ .dmypy.json
45
+ dmypy.json
46
+ .pyre/
47
+ .pytype/
48
+
49
+ # Example project local database
50
+ example/db.sqlite3
51
+
52
+ # Documentation build output
53
+ site/
54
+
55
+ # IDE files
56
+ # .idea/
57
+ # .vscode/
58
+
59
+ # Local agent guidance (not a committed team convention)
60
+ CLAUDE.md
61
+ example/db.sqlite3
62
+ .venv/
63
+ site/
@@ -0,0 +1,45 @@
1
+ ci:
2
+ autoupdate_schedule: monthly
3
+ skip: [mypy]
4
+
5
+ default_language_version:
6
+ python: python3.13
7
+
8
+ repos:
9
+ - repo: https://github.com/pre-commit/pre-commit-hooks
10
+ rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
11
+ hooks:
12
+ - id: check-added-large-files
13
+ - id: check-case-conflict
14
+ - id: check-merge-conflict
15
+ - id: check-toml
16
+ - id: check-yaml
17
+ - id: end-of-file-fixer
18
+ - id: trailing-whitespace
19
+
20
+ - repo: https://github.com/tox-dev/pyproject-fmt
21
+ rev: 3b42b9776870badc9eaffdef9a4a0834b764d87c # frozen: v2.29.3
22
+ hooks:
23
+ - id: pyproject-fmt
24
+
25
+ - repo: https://github.com/astral-sh/ruff-pre-commit
26
+ rev: 321478e58f4938179c6b86e4ddfa923d1547a49b # frozen: v0.16.6
27
+ hooks:
28
+ - id: ruff-check
29
+ args: [--fix]
30
+ - id: ruff-format
31
+
32
+ - repo: https://github.com/adamchainz/django-upgrade
33
+ rev: e668ea858b908d23a9702b24214f4fc5d07ed50f # frozen: 1.32.0
34
+ hooks:
35
+ - id: django-upgrade
36
+ args: [--target-version, "6.0"]
37
+
38
+ - repo: local
39
+ hooks:
40
+ - id: mypy
41
+ name: mypy
42
+ entry: uv run mypy src/ tests/ example/
43
+ language: system
44
+ types: [python]
45
+ pass_filenames: false
@@ -0,0 +1,44 @@
1
+ # django-huddle — agent guidance
2
+
3
+ A public, MIT-licensed Django package providing Slack-style chat (channels, DMs,
4
+ threads, @mentions) over Django Channels. Other projects depend on it, so the
5
+ **public API is a maintenance promise**.
6
+
7
+ ## Commands
8
+
9
+ ```bash
10
+ uv run pytest # quick run
11
+ uvx --with tox-uv tox run -f py314 # Django 6.0 + 6.1 matrix
12
+ uv run ruff format . && uv run ruff check --fix .
13
+ uv run mypy src/ tests/ example/
14
+ pre-commit run --all-files # before every commit
15
+ ```
16
+
17
+ ## Invariants
18
+
19
+ 1. **Every permission decision goes through `ChatPolicy`** (ADR-0002). Never add
20
+ an inline `if user ...` access check in a view, API view or consumer. Add or
21
+ extend a policy method instead.
22
+ 2. **Invisible means 404.** Look channels and messages up through
23
+ `Channel.objects.visible_to(user)`.
24
+ 3. **Every write goes through `ChatService`.** It keeps counters and read state
25
+ consistent and sends signals and broadcasts on commit.
26
+ 4. **Websocket events carry ids, never HTML** (ADR-0003). Each connection
27
+ re-checks and re-renders for its own user.
28
+ 5. **Three outputs, two builders** (ADR-0005). Templates use `ChatRenderer`
29
+ view models; the REST API and websocket JSON use `ChatPayloads`. Keep them in
30
+ step.
31
+ 6. **Stored bodies are tokens plus plain text** (ADR-0004). Never store HTML.
32
+
33
+ ## Style
34
+
35
+ - Django 6+, Python 3.12+, `from __future__ import annotations`, mypy strict.
36
+ - Query logic lives on QuerySets (`querysets.py`), with docstrings saying what
37
+ each method selects and where it's used.
38
+ - Guard clauses, max nesting depth 2, intention-revealing names.
39
+ - Docstrings carry the why. `#` comments are one line at most. No comments in
40
+ templates, JS or CSS unless essential.
41
+ - Each failure mode gets its own exception under `HuddleError`, carrying
42
+ `context`.
43
+ - One template per UI component under `templates/huddle/components/`.
44
+ - Every PR carries an ADR in `docs/adr/` for each distinct decision.
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (unreleased)
4
+
5
+ First release.
6
+
7
+ - Channels (public and private), direct and group messages, one-level threads, @mentions.
8
+ - `ChatPolicy` permission hook with scoped channels.
9
+ - `ChatService` for every write, with `message_posted`, `user_mentioned` and `thread_replied` signals.
10
+ - htmx UI built from overridable component templates.
11
+ - Websocket consumer (HTML or JSON mode) and `HuddleConsumerMixin`.
12
+ - Optional REST API (`django-huddle[api]`).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026, Ben Atkinson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.