diff --git a/CHANGES.md b/CHANGES.md
index d0faf7c..d753a24 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -15,6 +15,7 @@
 <!-- Changes that affect Black's preview style -->
 
 - Code cell separators `#%%` are now standardised to `# %%` (#2919)
+- Avoid magic-trailing-comma in single-element subscripts (#2942)
 
 ### _Blackd_
 
diff --git a/docs/guides/introducing_black_to_your_project.md b/docs/guides/introducing_black_to_your_project.md
index 71ccf7c..9ae40a1 100644
--- a/docs/guides/introducing_black_to_your_project.md
+++ b/docs/guides/introducing_black_to_your_project.md
@@ -43,8 +43,10 @@ call to `git blame`.
 $ git config blame.ignoreRevsFile .git-blame-ignore-revs
 ```
 
-**The one caveat is that GitHub and GitLab do not yet support ignoring revisions using
-their native UI of blame.** So blame information will be cluttered with a reformatting
-commit on those platforms. (If you'd like this feature, there's an open issue for
-[GitLab](https://gitlab.com/gitlab-org/gitlab/-/issues/31423) and please let GitHub
-know!)
+**The one caveat is that some online Git-repositories like GitLab do not yet support
+ignoring revisions using their native blame UI.** So blame information will be cluttered
+with a reformatting commit on those platforms. (If you'd like this feature, there's an
+open issue for [GitLab](https://gitlab.com/gitlab-org/gitlab/-/issues/31423)). This is
+however supported by
+[GitHub](https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view),
+currently in beta.
diff --git a/src/black/linegen.py b/src/black/linegen.py
index 79475a8..5d92011 100644
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -8,7 +8,12 @@ from typing import Collection, Iterator, List, Optional, Set, Union
 from black.nodes import WHITESPACE, RARROW, STATEMENT, STANDALONE_COMMENT
 from black.nodes import ASSIGNMENTS, OPENING_BRACKETS, CLOSING_BRACKETS
 from black.nodes import Visitor, syms, is_arith_like, ensure_visible
-from black.nodes import is_docstring, is_empty_tuple, is_one_tuple, is_one_tuple_between
+from black.nodes import (
+    is_docstring,
+    is_empty_tuple,
+    is_one_tuple,
+    is_one_sequence_between,
+)
 from black.nodes import is_name_token, is_lpar_token, is_rpar_token
 from black.nodes import is_walrus_assignment, is_yield, is_vararg, is_multiline_string
 from black.nodes import is_stub_suite, is_stub_body, is_atom_with_invisible_parens
@@ -973,7 +978,7 @@ def generate_trailers_to_omit(line: Line, line_length: int) -> Iterator[Set[Leaf
                     prev
                     and prev.type == token.COMMA
                     and leaf.opening_bracket is not None
-                    and not is_one_tuple_between(
+                    and not is_one_sequence_between(
                         leaf.opening_bracket, leaf, line.leaves
                     )
                 ):
@@ -1001,7 +1006,7 @@ def generate_trailers_to_omit(line: Line, line_length: int) -> Iterator[Set[Leaf
                 prev
                 and prev.type == token.COMMA
                 and leaf.opening_bracket is not None
-                and not is_one_tuple_between(leaf.opening_bracket, leaf, line.leaves)
+                and not is_one_sequence_between(leaf.opening_bracket, leaf, line.leaves)
             ):
                 # Never omit bracket pairs with trailing commas.
                 # We need to explode on those.
diff --git a/src/black/lines.py b/src/black/lines.py
index f35665c..e455a50 100644
--- a/src/black/lines.py
+++ b/src/black/lines.py
@@ -17,12 +17,12 @@ from blib2to3.pytree import Node, Leaf
 from blib2to3.pgen2 import token
 
 from black.brackets import BracketTracker, DOT_PRIORITY
-from black.mode import Mode
+from black.mode import Mode, Preview
 from black.nodes import STANDALONE_COMMENT, TEST_DESCENDANTS
 from black.nodes import BRACKETS, OPENING_BRACKETS, CLOSING_BRACKETS
 from black.nodes import syms, whitespace, replace_child, child_towards
 from black.nodes import is_multiline_string, is_import, is_type_comment
-from black.nodes import is_one_tuple_between
+from black.nodes import is_one_sequence_between
 
 # types
 T = TypeVar("T")
@@ -254,6 +254,7 @@ class Line:
         """Return True if we have a magic trailing comma, that is when:
         - there's a trailing comma here
         - it's not a one-tuple
+        - it's not a single-element subscript
         Additionally, if ensure_removable:
         - it's not from square bracket indexing
         """
@@ -268,6 +269,20 @@ class Line:
             return True
 
         if closing.type == token.RSQB:
+            if (
+                Preview.one_element_subscript in self.mode
+                and closing.parent
+                and closing.parent.type == syms.trailer
+                and closing.opening_bracket
+                and is_one_sequence_between(
+                    closing.opening_bracket,
+                    closing,
+                    self.leaves,
+                    brackets=(token.LSQB, token.RSQB),
+                )
+            ):
+                return False
+
             if not ensure_removable:
                 return True
             comma = self.leaves[-1]
@@ -276,7 +291,7 @@ class Line:
         if self.is_import:
             return True
 
-        if closing.opening_bracket is not None and not is_one_tuple_between(
+        if closing.opening_bracket is not None and not is_one_sequence_between(
             closing.opening_bracket, closing, self.leaves
         ):
             return True
diff --git a/src/black/mode.py b/src/black/mode.py
index 35a072c..77b1cab 100644
--- a/src/black/mode.py
+++ b/src/black/mode.py
@@ -127,6 +127,7 @@ class Preview(Enum):
     """Individual preview style features."""
 
     string_processing = auto()
+    one_element_subscript = auto()
 
 
 class Deprecated(UserWarning):
@@ -162,9 +163,7 @@ class Mode:
         """
         if feature is Preview.string_processing:
             return self.preview or self.experimental_string_processing
-        # TODO: Remove type ignore comment once preview contains more features
-        #  than just ESP
-        return self.preview  # type: ignore
+        return self.preview
 
     def get_cache_key(self) -> str:
         if self.target_versions:
diff --git a/src/black/nodes.py b/src/black/nodes.py
index f130bff..d18d4bd 100644
--- a/src/black/nodes.py
+++ b/src/black/nodes.py
@@ -9,6 +9,7 @@ from typing import (
     List,
     Optional,
     Set,
+    Tuple,
     TypeVar,
     Union,
 )
@@ -559,9 +560,14 @@ def is_one_tuple(node: LN) -> bool:
     )
 
 
-def is_one_tuple_between(opening: Leaf, closing: Leaf, leaves: List[Leaf]) -> bool:
-    """Return True if content between `opening` and `closing` looks like a one-tuple."""
-    if opening.type != token.LPAR and closing.type != token.RPAR:
+def is_one_sequence_between(
+    opening: Leaf,
+    closing: Leaf,
+    leaves: List[Leaf],
+    brackets: Tuple[int, int] = (token.LPAR, token.RPAR),
+) -> bool:
+    """Return True if content between `opening` and `closing` is a one-sequence."""
+    if (opening.type, closing.type) != brackets:
         return False
 
     depth = closing.bracket_depth + 1

diff --git a/tests/data/one_element_subscript.py b/tests/data/one_element_subscript.py
new file mode 100644
index 0000000..39205ba
--- /dev/null
+++ b/tests/data/one_element_subscript.py
@@ -0,0 +1,36 @@
+# We should not treat the trailing comma
+# in a single-element subscript.
+a: tuple[int,]
+b = tuple[int,]
+
+# The magic comma still applies to multi-element subscripts.
+c: tuple[int, int,]
+d = tuple[int, int,]
+
+# Magic commas still work as expected for non-subscripts.
+small_list = [1,]
+list_of_types = [tuple[int,],]
+
+# output
+# We should not treat the trailing comma
+# in a single-element subscript.
+a: tuple[int,]
+b = tuple[int,]
+
+# The magic comma still applies to multi-element subscripts.
+c: tuple[
+    int,
+    int,
+]
+d = tuple[
+    int,
+    int,
+]
+
+# Magic commas still work as expected for non-subscripts.
+small_list = [
+    1,
+]
+list_of_types = [
+    tuple[int,],
+]
diff --git a/tests/test_format.py b/tests/test_format.py
index 667d5c1..4de3170 100644
--- a/tests/test_format.py
+++ b/tests/test_format.py
@@ -80,6 +80,7 @@ PREVIEW_CASES: List[str] = [
     "long_strings__edge_case",
     "long_strings__regression",
     "percent_precedence",
+    "one_element_subscript",
 ]
 
 SOURCES: List[str] = [

Avoid magic-trailing-comma newline in `tuple[int,]`, like tuple literal `(1,)`
Black exempts single-element tuple literals from the usual handling of magic trailing commas: `(1,)` will *not* have a newline added (as would happen for lists, sets, etc.).  

I'd like to be able to write type annotations for a tuple-of-one-element in the same way, so that I can write either `tuple[int,]` or `tuple[int, ...]` without looking quite so much like `list[int]`.  However, Black's current add-a-newline code style makes this pattern unattractive, so I'm unlikely to write a linter for this pattern unless the code style changes.

**Examples in the current _Black_ style**

```python
shape: tuple[
    int,
] 
shape = (1,)
```

**Desired style**

```python
shape: tuple[int,]
shape = (1,)
```
tests/test_format.py::test_preview_format[one_element_subscript]
tests/test_format.py::test_simple_format[attribute_access_on_number_literals] tests/test_format.py::test_simple_format[beginning_backslash] tests/test_format.py::test_simple_format[bracketmatch] tests/test_format.py::test_simple_format[class_blank_parentheses] tests/test_format.py::test_simple_format[class_methods_new_line] tests/test_format.py::test_simple_format[collections] tests/test_format.py::test_simple_format[comments] tests/test_format.py::test_simple_format[comments2] tests/test_format.py::test_simple_format[comments3] tests/test_format.py::test_simple_format[comments4] tests/test_format.py::test_simple_format[comments5] tests/test_format.py::test_simple_format[comments6] tests/test_format.py::test_simple_format[comments_non_breaking_space] tests/test_format.py::test_simple_format[comment_after_escaped_newline] tests/test_format.py::test_simple_format[composition] tests/test_format.py::test_simple_format[composition_no_trailing_comma] tests/test_format.py::test_simple_format[docstring] tests/test_format.py::test_simple_format[empty_lines] tests/test_format.py::test_simple_format[expression] tests/test_format.py::test_simple_format[fmtonoff] tests/test_format.py::test_simple_format[fmtonoff2] tests/test_format.py::test_simple_format[fmtonoff3] tests/test_format.py::test_simple_format[fmtonoff4] tests/test_format.py::test_simple_format[fmtskip] tests/test_format.py::test_simple_format[fmtskip2] tests/test_format.py::test_simple_format[fmtskip3] tests/test_format.py::test_simple_format[fmtskip4] tests/test_format.py::test_simple_format[fmtskip5] tests/test_format.py::test_simple_format[fmtskip6] tests/test_format.py::test_simple_format[fstring] tests/test_format.py::test_simple_format[function] tests/test_format.py::test_simple_format[function2] tests/test_format.py::test_simple_format[function_trailing_comma] tests/test_format.py::test_simple_format[import_spacing] tests/test_format.py::test_simple_format[power_op_spacing] tests/test_format.py::test_simple_format[remove_parens] tests/test_format.py::test_simple_format[slices] tests/test_format.py::test_simple_format[string_prefixes] tests/test_format.py::test_simple_format[torture] tests/test_format.py::test_simple_format[trailing_comma_optional_parens1] tests/test_format.py::test_simple_format[trailing_comma_optional_parens2] tests/test_format.py::test_simple_format[trailing_comma_optional_parens3] tests/test_format.py::test_simple_format[tricky_unicode_symbols] tests/test_format.py::test_simple_format[tupleassign] tests/test_format.py::test_preview_format[cantfit] tests/test_format.py::test_preview_format[comments7] tests/test_format.py::test_preview_format[comments8] tests/test_format.py::test_preview_format[long_strings] tests/test_format.py::test_preview_format[long_strings__edge_case] tests/test_format.py::test_preview_format[long_strings__regression] tests/test_format.py::test_preview_format[percent_precedence] tests/test_format.py::test_source_is_formatted[src/black/__init__.py] tests/test_format.py::test_source_is_formatted[src/black/__main__.py] tests/test_format.py::test_source_is_formatted[src/black/brackets.py] tests/test_format.py::test_source_is_formatted[src/black/cache.py] tests/test_format.py::test_source_is_formatted[src/black/comments.py] tests/test_format.py::test_source_is_formatted[src/black/concurrency.py] tests/test_format.py::test_source_is_formatted[src/black/const.py] tests/test_format.py::test_source_is_formatted[src/black/debug.py] tests/test_format.py::test_source_is_formatted[src/black/files.py] tests/test_format.py::test_source_is_formatted[src/black/linegen.py] tests/test_format.py::test_source_is_formatted[src/black/lines.py] tests/test_format.py::test_source_is_formatted[src/black/mode.py] tests/test_format.py::test_source_is_formatted[src/black/nodes.py] tests/test_format.py::test_source_is_formatted[src/black/numerics.py] tests/test_format.py::test_source_is_formatted[src/black/output.py] tests/test_format.py::test_source_is_formatted[src/black/parsing.py] tests/test_format.py::test_source_is_formatted[src/black/report.py] tests/test_format.py::test_source_is_formatted[src/black/rusty.py] tests/test_format.py::test_source_is_formatted[src/black/strings.py] tests/test_format.py::test_source_is_formatted[src/black/trans.py] tests/test_format.py::test_source_is_formatted[src/blackd/__init__.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pygram.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pytree.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pgen2/conv.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pgen2/driver.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pgen2/grammar.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pgen2/literals.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pgen2/parse.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pgen2/pgen.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pgen2/tokenize.py] tests/test_format.py::test_source_is_formatted[src/blib2to3/pgen2/token.py] tests/test_format.py::test_source_is_formatted[setup.py] tests/test_format.py::test_source_is_formatted[tests/test_black.py] tests/test_format.py::test_source_is_formatted[tests/test_blackd.py] tests/test_format.py::test_source_is_formatted[tests/test_format.py] tests/test_format.py::test_source_is_formatted[tests/optional.py] tests/test_format.py::test_source_is_formatted[tests/util.py] tests/test_format.py::test_source_is_formatted[tests/conftest.py] tests/test_format.py::test_empty tests/test_format.py::test_pep_572 tests/test_format.py::test_pep_572_remove_parens tests/test_format.py::test_pep_572_do_not_remove_parens tests/test_format.py::test_pep_572_newer_syntax[3-9] tests/test_format.py::test_pep_572_newer_syntax[3-10] tests/test_format.py::test_pep_570 tests/test_format.py::test_python_310[starred_for_target] tests/test_format.py::test_python_310[pattern_matching_simple] tests/test_format.py::test_python_310[pattern_matching_complex] tests/test_format.py::test_python_310[pattern_matching_extras] tests/test_format.py::test_python_310[pattern_matching_style] tests/test_format.py::test_python_310[pattern_matching_generic] tests/test_format.py::test_python_310[parenthesized_context_managers] tests/test_format.py::test_python_310_without_target_version tests/test_format.py::test_patma_invalid tests/test_format.py::test_python_2_hint tests/test_format.py::test_docstring_no_string_normalization tests/test_format.py::test_long_strings_flag_disabled tests/test_format.py::test_numeric_literals tests/test_format.py::test_numeric_literals_ignoring_underscores tests/test_format.py::test_stub tests/test_format.py::test_python38 tests/test_format.py::test_python39 tests/test_format.py::test_power_op_newline